Come posso raccogliere i dati del cliente su Google App Engine senza utilizzare troppo le istanze di Datastore / Backend?

6

Uno dei progetti a cui sto lavorando è il motore di sondaggi online. È il mio primo grande progetto commerciale su Google App Engine.

Ho bisogno del tuo consiglio su come raccogliere statistiche e registrarle in modo efficiente in DataStore senza bancarotta. I requisiti iniziali sono:

  • Dopo che l'utente ha terminato il sondaggio, il cliente invia un elenco di coppie [ID (int) + PercentHit (doppio)]. Questo elenco mostra come le risposte ravvicinate di questo utente corrispondono alle risposte predefinite dei rispondenti di riferimento (identificati dagli ID). Li chiamo "ID di destinazione".
  • Il creatore del sondaggio vuole vedere% aggregato per gli ID dati per l'ultima ora, periodo temporale particolare o dall'inizio del sondaggio.
  • Alcuni sondaggi potrebbero contenere migliaia di rispondenti di riferimento / target.

Quindi ho creato un'entità

public class HitsStatsDO implements Serializable
{
    @Id
    transient private Long id;
    transient private Long version = (long) 0;

    transient private Long startDate;

    @Parent transient private Key parent;   // fake parent which contains target id
    @Transient int targetId;

    private double avgPercent;
    private long hitCount;
}

Ma scrivere HitsStatsDO per ogni target da ciascun utente fornirebbe molti dati. Ad esempio, ho avuto un sondaggio con 3000 obiettivi a cui hanno risposto circa 4 milioni di persone in una settimana con 300.000 persone che hanno partecipato al sondaggio nel primo giorno. Anche supponendo che stessero rispondendo in modo uniforme per 24 ore, ci avrebbe dato ~ 1040 scritture / secondo. Ovviamente colpisce il limite di scritture concorrenti di Datastore.

Ho deciso di raccogliere i dati per un'ora e di salvarlo, ecco perché ci sono avgPercent e hitCount in HitsStatsDO . Le istanze GAE sono prive di stato, quindi ho dovuto utilizzare istanza di backend dinamica .

Lì ho qualcosa di simile a questo:

// Contains stats for one hour
private class Shard
{
    ReadWriteLock lock = new ReentrantReadWriteLock();
    Map<Integer, HitsStatsDO> map = new HashMap<Integer, HitsStatsDO>(); // Key is target ID

    public void saveToDatastore();
    public void updateStats(Long startDate, Map<Integer, Double> hits);
}

e mappare con il frammento per l'ora corrente e l'ora precedente (che non rimane qui a lungo)

private HashMap<Long, Shard> shards = new HashMap<Long, Shard>();   // Key is HitsStatsDO.startDate

Quindi una volta all'ora eseguo il dump di Shard per l'ora precedente su Datastore.

Inoltre ho class LifetimeStats che mantiene Map<Integer, HitsStatsDO> in memcached dove map-key è ID di destinazione.

Anche nel mio metodo di hook backend shutdown scarico statistiche per data non finita su Datastore.

C'è solo un grosso problema qui - Ho solo UN'UNICA backend :) Solleva le seguenti domande su cui mi piacerebbe sentire la tua opinione:

  • Posso farlo senza utilizzare l'istanza di back-end?
  • Che cosa succede se un'istanza non è sufficiente?
  • Come posso dividere i dati tra più istanze di backend dinamiche ? È difficile perché non so quanti ne ho perché Google ne crea uno nuovo quando il carico aumenta.
  • So di poter avviare il numero esatto di istanze di backend residenti . Ma quanti? 2, 5, 10? Cosa succede se non ho alcun carico per una settimana. L'esecuzione costante di 10 istanze di backend è troppo costosa.
  • Che cosa faccio con i dati dei client mentre l'istanza di backend è morta / sta riavviando?

Una cosa da notare è che non posso cambiare molto il cliente. Attualmente è JavaScript incorporato nelle pagine Web dei clienti. Posso cambiare RPC in qualche modo, ma architettonicamente non posso sostituire il client con i moduli di Google Documenti, per esempio.

    
posta expert 20.11.2011 - 02:31
fonte

1 risposta

2

Il mio servizio è stato pubblicato e voglio condividere come l'ho implementato.

Quindi, invece di raccogliere dati in memoria di una singola istanza di backend per un'ora, ho deciso di raccoglierlo in più istanze di backend dinamiche e aggiornare lo shard per l'ora corrente in Datastore ogni 10 minuti da ogni istanza. La classe Shard rimane la stessa con l'eccezione di saveToDatastore() dove ora aggiorno HitsStatsDOs nel ciclo di transazione per assicurarmi che sia aggiornato anche se un'altra istanza di backend modifica shard al momento.

Per recuperare HitsStatsDO molto velocemente ho deciso di inserire l'ID di destinazione nella falsa chiave genitore e nel timestamp se questo difficile da ID primario come questo

public class HitsStatsDO implements Serializable
{
    @Id
    transient private Long id;  // always equals to "startDate"
    @Unindexed
    transient private Long version = (long) 0;

    transient private Long startDate;

    @Parent
    transient private Key targetIdKey;   // fake parent which contains target id

    @Unindexed
    private double avgPercent;
    @Unindexed
    private long hitCount;

    public Key<HitsStatsDO> createKey()
    {
        return new Key<HitsStatsDO>(targetIdKey, HitsStatsDO.class, startDate);
    }

    public HitsStatsDO(Long startDate, long targetId)
    {
        this.id = this.startDate = startDate;
        this.targetIdKey = new Key(Long.class, targetId);
    }
}

Questa entità richiede solo 2 scritture da memorizzare. La quantità di scritture non è mai superiore ([quantità di istanze di backend] * 2 * 6) all'ora che non è male. Inoltre posso pre-creare chiavi nel mio codice e fare batch-get da Datastore.

Allo stesso modo ho cambiato HitsStatsTotalDO che contiene le statistiche dall'inizio del sondaggio. Sembra questo

public class HitsStatsTotalDO implements Serializable
{
    @Id
    private Long targetId;
    @Unindexed
    transient private Long version = (long) 0;

    @Unindexed
    private double avgPercent;
    @Unindexed
    private long hitCount;
}

La stessa cosa: 2 scrive per memorizzare / aggiornare.

Il servizio è andato in diretta 3 giorni fa. Il carico massimo finora era di 230 QPS. Sto usando istanze di tipo B1 dinamiche. In config ho impostato il massimo di 4 istanze per ora ma a mio piacere GAE non ha mai istanziato più di una. E sorprendentemente non ho ancora avuto eccezioni di concorrenza.

Fammi sapere se hai qualche domanda o pensi che mi sia sfuggito qualcosa.

E grazie a tutti per il vostro aiuto. StackExchange è una community davvero fantastica.

    
risposta data 05.12.2011 - 08:22
fonte