Come caricare 100 milioni di dischi in MongoDB con Scala per il test delle prestazioni?

Ho un piccolo script che è scritto in Scala, che è destinato per caricare un MongoDB istanza con 100.000.000 di record di esempio. L’idea è di ottenere il DB tutti caricati, e poi fare un po di test delle prestazioni (e tune/re-load, se necessario).

Il problema è che il carico in tempo per 100.000 record aumenta abbastanza linearmente. All’inizio del mio processo di caricamento ci sono voluti solo 4 secondi per caricare i record. Ora, a quasi 6.000.000 di record, è fra i 300 e i 400 secondi per caricare la stessa quantità (circa 100.000)! Per due ordini di grandezza più lento! Le query sono ancora scattante, ma a questo ritmo, non sarò mai in grado di caricare la quantità di dati che vorrei.

Questo lavoro più velocemente se posso scrivere un file con tutti i miei dischi (tutti 100,000,000!), e quindi utilizzare mongoimport per importare il tutto? O sono le mie aspettative troppo alte e sto usando il DB al di là di quello che si suppone in grado di gestire?

Ogni pensiero? Grazie!

Ecco il mio script:

import java.util.Date

import com.mongodb.casbah.Imports._
import com.mongodb.casbah.commons.MongoDBObject

object MongoPopulateTest {
  val ONE_HUNDRED_THOUSAND = 100000
  val ONE_MILLION          = ONE_HUNDRED_THOUSAND * 10

  val random     = new scala.util.Random(12345)
  val connection = MongoConnection()
  val db         = connection("mongoVolumeTest")
  val collection = db("testData")

  val INDEX_KEYS = List("A", "G", "E", "F")

  def main(args: Array[String]) {
    populateCoacs(ONE_MILLION * 100)
  }

  def populateCoacs(count: Int) {
    println("Creating indexes: " + INDEX_KEYS.mkString(", "))
    INDEX_KEYS.map(key => collection.ensureIndex(MongoDBObject(key -> 1)))

    println("Adding " + count + " records to DB.")

    val start     = (new Date()).getTime()
    var lastBatch = start

    for(i <- 0 until count) {
      collection.save(makeCoac())
      if(i % 100000 == 0 && i != 0) {
        println(i + ": " + (((new Date()).getTime() - lastBatch) / 1000.0) + " seconds (" +  (new Date()).toString() + ")")
        lastBatch = (new Date()).getTime()
      }
    }

    val elapsedSeconds = ((new Date).getTime() - start) / 1000

    println("Done. " + count + " COAC rows inserted in " + elapsedSeconds + " seconds.")
  }

  def makeCoac(): MongoDBObject = {
    MongoDBObject(
      "A" -> random.nextPrintableChar().toString(),
      "B" -> scala.math.abs(random.nextInt()),
      "C" -> makeRandomPrintableString(50),
      "D" -> (if(random.nextBoolean()) { "Cd" } else { "Cc" }),
      "E" -> makeRandomPrintableString(15),
      "F" -> makeRandomPrintableString(15),
      "G" -> scala.math.abs(random.nextInt()),
      "H" -> random.nextBoolean(),
      "I" -> (if(random.nextBoolean()) { 41 } else { 31 }),
      "J" -> (if(random.nextBoolean()) { "A" } else { "B" }),
      "K" -> random.nextFloat(),
      "L" -> makeRandomPrintableString(15),
      "M" -> makeRandomPrintableString(15),
      "N" -> scala.math.abs(random.nextInt()),
      "O" -> random.nextFloat(),
      "P" -> (if(random.nextBoolean()) { "USD" } else { "GBP" }),
      "Q" -> (if(random.nextBoolean()) { "PROCESSED" } else { "UNPROCESSED" }),
      "R" -> scala.math.abs(random.nextInt())
    )
  }

  def makeRandomPrintableString(length: Int): String = {
    var result = ""
    for(i <- 0 until length) {
      result += random.nextPrintableChar().toString()
    }
    result
  }
}

Ecco l’output del mio script:

Creating indexes: A, G, E, F
Adding 100000000 records to DB.
100000: 4.456 seconds (Thu Jul 21 15:18:57 EDT 2011)
200000: 4.155 seconds (Thu Jul 21 15:19:01 EDT 2011)
300000: 4.284 seconds (Thu Jul 21 15:19:05 EDT 2011)
400000: 4.32 seconds (Thu Jul 21 15:19:10 EDT 2011)
500000: 4.597 seconds (Thu Jul 21 15:19:14 EDT 2011)
600000: 4.412 seconds (Thu Jul 21 15:19:19 EDT 2011)
700000: 4.435 seconds (Thu Jul 21 15:19:23 EDT 2011)
800000: 5.919 seconds (Thu Jul 21 15:19:29 EDT 2011)
900000: 4.517 seconds (Thu Jul 21 15:19:33 EDT 2011)
1000000: 4.483 seconds (Thu Jul 21 15:19:38 EDT 2011)
1100000: 4.78 seconds (Thu Jul 21 15:19:43 EDT 2011)
1200000: 9.643 seconds (Thu Jul 21 15:19:52 EDT 2011)
1300000: 25.479 seconds (Thu Jul 21 15:20:18 EDT 2011)
1400000: 30.028 seconds (Thu Jul 21 15:20:48 EDT 2011)
1500000: 24.531 seconds (Thu Jul 21 15:21:12 EDT 2011)
1600000: 18.562 seconds (Thu Jul 21 15:21:31 EDT 2011)
1700000: 28.48 seconds (Thu Jul 21 15:21:59 EDT 2011)
1800000: 29.127 seconds (Thu Jul 21 15:22:29 EDT 2011)
1900000: 25.814 seconds (Thu Jul 21 15:22:54 EDT 2011)
2000000: 16.658 seconds (Thu Jul 21 15:23:11 EDT 2011)
2100000: 24.564 seconds (Thu Jul 21 15:23:36 EDT 2011)
2200000: 32.542 seconds (Thu Jul 21 15:24:08 EDT 2011)
2300000: 30.378 seconds (Thu Jul 21 15:24:39 EDT 2011)
2400000: 21.188 seconds (Thu Jul 21 15:25:00 EDT 2011)
2500000: 23.923 seconds (Thu Jul 21 15:25:24 EDT 2011)
2600000: 46.077 seconds (Thu Jul 21 15:26:10 EDT 2011)
2700000: 104.434 seconds (Thu Jul 21 15:27:54 EDT 2011)
2800000: 23.344 seconds (Thu Jul 21 15:28:17 EDT 2011)
2900000: 17.206 seconds (Thu Jul 21 15:28:35 EDT 2011)
3000000: 19.15 seconds (Thu Jul 21 15:28:54 EDT 2011)
3100000: 14.488 seconds (Thu Jul 21 15:29:08 EDT 2011)
3200000: 20.916 seconds (Thu Jul 21 15:29:29 EDT 2011)
3300000: 69.93 seconds (Thu Jul 21 15:30:39 EDT 2011)
3400000: 81.178 seconds (Thu Jul 21 15:32:00 EDT 2011)
3500000: 93.058 seconds (Thu Jul 21 15:33:33 EDT 2011)
3600000: 168.613 seconds (Thu Jul 21 15:36:22 EDT 2011)
3700000: 189.917 seconds (Thu Jul 21 15:39:32 EDT 2011)
3800000: 200.971 seconds (Thu Jul 21 15:42:53 EDT 2011)
3900000: 207.728 seconds (Thu Jul 21 15:46:21 EDT 2011)
4000000: 213.778 seconds (Thu Jul 21 15:49:54 EDT 2011)
4100000: 219.32 seconds (Thu Jul 21 15:53:34 EDT 2011)
4200000: 241.545 seconds (Thu Jul 21 15:57:35 EDT 2011)
4300000: 193.555 seconds (Thu Jul 21 16:00:49 EDT 2011)
4400000: 190.949 seconds (Thu Jul 21 16:04:00 EDT 2011)
4500000: 184.433 seconds (Thu Jul 21 16:07:04 EDT 2011)
4600000: 231.709 seconds (Thu Jul 21 16:10:56 EDT 2011)
4700000: 243.0 seconds (Thu Jul 21 16:14:59 EDT 2011)
4800000: 310.156 seconds (Thu Jul 21 16:20:09 EDT 2011)
4900000: 318.421 seconds (Thu Jul 21 16:25:28 EDT 2011)
5000000: 378.112 seconds (Thu Jul 21 16:31:46 EDT 2011)
5100000: 265.648 seconds (Thu Jul 21 16:36:11 EDT 2011)
5200000: 295.086 seconds (Thu Jul 21 16:41:06 EDT 2011)
5300000: 297.678 seconds (Thu Jul 21 16:46:04 EDT 2011)
5400000: 329.256 seconds (Thu Jul 21 16:51:33 EDT 2011)
5500000: 336.571 seconds (Thu Jul 21 16:57:10 EDT 2011)
5600000: 398.64 seconds (Thu Jul 21 17:03:49 EDT 2011)
5700000: 351.158 seconds (Thu Jul 21 17:09:40 EDT 2011)
5800000: 410.561 seconds (Thu Jul 21 17:16:30 EDT 2011)
5900000: 689.369 seconds (Thu Jul 21 17:28:00 EDT 2011)
  • I dati devono utilizzare almeno qualcosa come 8 gigabyte di memoria. Anche l’indice dovrebbe prendere almeno la metà di un gigabyte. Hai assicurarsi che il DB può rientrare in RAM? Io non sono un esperto di MongoDB con qualsiasi mezzo, ma suppongo che si potrebbe arrivare a rilento a causa di swapping.
  • Tenta di aggiungere indice dopo aver inserito i dati, questo dovrebbe migliorare le prestazioni dell’inserto.



4 Replies
  1. 51

    Alcuni consigli :

    1. Non indicizzare la vostra collezione prima di inserire, come inserti di modificare l’indice che è un overhead. Inserire tutto, e quindi creare un indice .

    2. invece di “salva” , l’utilizzo di mongoDB “batchinsert” che può inserire il numero di record in 1 operazione. Così sono circa 5000 i documenti inseriti per lotto.
      Vedrete un notevole guadagno in termini di prestazioni .

      vedere il metodo#2 di inserire qui, prende gamma di documenti per inserire invece di un singolo documento.
      Vedi anche la discussione in questo thread

      E se si desidera benchmark di più –

    3. Questa è solo una supposizione, provare a utilizzare un tappo di raccolta di un set predefinito di grandi dimensioni per archiviare tutti i tuoi dati. Capped collezione senza indice è di ottimo rendimento dell’inserimento.

    • Bello. Io sono a ~5 secondi per 100k oggetti utilizzando casbah di raccolta.inserisci(Lista[MongoDBObject](…)) con lotti di 5000 euro a inserire (e non di indici). Ben oltre 15 milioni di fatto, in pochi minuti. Per arrivare a questo stesso numero in precedenza, ho dovuto correre per una notte. Grazie!
    • 100 milioni caricato in 1 ora e 20 minuti. Ciascuno dei quattro indici sembra prendere circa un’ora per creare, quindi, si dovrebbe prendere circa 5 ore in totale. Non male. Grazie di nuovo.
    • Suona alla grande. Sei il benvenuto .
    • Che ne dite di un po ‘ di multithreading? Una era leggermente migliore cifre con il numero di thread pari al numero di processori
    • questo è un buon suggerimento, Potete per favore mettere più dettagli su di esso come un separato risposta qui per il beneficio di tutti.
    • Per #1, MongoDB in Azione(manning.com/banker) dice If you need a unique index on a collection, it’s usually best to create the index before inserting any data.

  2. 6

    Ho avuto la stessa cosa. Per quanto posso dire, si tratta di casualità dei valori di indice. Ogni volta che un nuovo documento è inserito, anche, ovviamente, di necessità di aggiornamento di tutti gli indici sottostanti. Poiché si stanno inserendo casuale, anziché sequenziale, i valori di questi indici, si sta continuamente l’accesso all’intero indice per trovare dove inserire il nuovo valore.

    Questo va bene per cominciare, quando tutti gli indici sono seduto tranquillamente in memoria, ma appena crescono troppo grande, è necessario iniziare a colpire il disco per fare inserimenti indice, poi il disco inizia a botte e le prestazioni di scrittura muore.

    Come si caricano i dati, prova a confrontare db.collection.totalIndexSize() con la memoria disponibile, e probabilmente vedrete che questo accada.

    La vostra scommessa migliore è quello di creare gli indici dopo aver caricato i dati. Tuttavia, questo ancora non risolve il problema quando è richiesto _id indice che contiene un valore casuale (GUID, hash, etc.), quindi il tuo approccio migliore potrebbe essere quello di pensare di sharding o di ottenere più di RAM.

  3. 4

    Quello che ho fatto nel mio progetto è stata l’aggiunta di un po ‘ di multithreading (il progetto è in C#, ma spero che il codice è auto-esplicativo). Dopo aver giocato con il necessario numero di thread si è scoperto che il numero di thread per il numero di core che porta a una performance leggermente migliore(10-20%), ma suppongo che questo boost è un hardware specifico. Ecco il codice:

        public virtual void SaveBatch(IEnumerable<object> entities)
        {
            if (entities == null)
                throw new ArgumentNullException("entities");
    
            _repository.SaveBatch(entities);
        }
    
    
        public void ParallelSaveBatch(IEnumerable<IEnumerable<object>> batchPortions)
        {
            if (batchPortions == null)
                throw new ArgumentNullException("batchPortions");
            var po = new ParallelOptions
                     {
                         MaxDegreeOfParallelism = Environment.ProcessorCount
                     };
            Parallel.ForEach(batchPortions, po, SaveBatch);
        }

Lascia un commento