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.