При экспериментировании с некоторыми коллекциями временных рядов мне понадобился большой набор данных, чтобы убедиться, что наши запросы агрегации не становятся узким местом в случае увеличения загрузки данных. Мы согласились на 50 миллионов документов, так как помимо этого числа мы все равно рассмотрим возможность шардинга.
Каждый раз событие выглядит так:
1
2
3
4
5
|
{ "_id" : ObjectId( "5298a5a03b3f4220588fe57c" ), "created_on" : ISODate( "2012-04-22T01:09:53Z" ), "value" : 0.1647851116706831 } |
Поскольку мы хотели получить случайные значения, мы подумали о том, чтобы сгенерировать их с помощью JavaScript или Python (мы могли бы попробовать в Java, но мы хотели написать это как можно быстрее). Мы не знали, какой из них будет быстрее, поэтому мы решили проверить их.
Нашей первой попыткой был запуск файла JavaScript через оболочку MongoDB.
Вот как это выглядит:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
var minDate = new Date( 2012 , 0 , 1 , 0 , 0 , 0 , 0 ); var maxDate = new Date( 2013 , 0 , 1 , 0 , 0 , 0 , 0 ); var delta = maxDate.getTime() - minDate.getTime(); var job_id = arg2; var documentNumber = arg1; var batchNumber = 5 * 1000 ; var job_name = 'Job#' + job_id var start = new Date(); var batchDocuments = new Array(); var index = 0 ; while (index < documentNumber) { var date = new Date(minDate.getTime() + Math.random() * delta); var value = Math.random(); var document = { created_on : date, value : value }; batchDocuments[index % batchNumber] = document; if ((index + 1 ) % batchNumber == 0 ) { db.randomData.insert(batchDocuments); } index++; if (index % 100000 == 0 ) { print(job_name + ' inserted ' + index + ' documents.' ); } } print(job_name + ' inserted ' + documentNumber + ' in ' + ( new Date() - start)/ 1000.0 + 's' ); |
Вот как мы его запустили и что получили:
1
2
3
4
5
6
7
|
mongo random -- eval "var arg1=50000000;arg2=1" create_random.js Job #1 inserted 100000 documents. Job #1 inserted 200000 documents. Job #1 inserted 300000 documents. ... Job #1 inserted 49900000 documents. Job #1 inserted 50000000 in 566.294s |
Ну, это уже выше моих диких ожиданий (88293 вставок в секунду).
Теперь настала очередь Python. Вам нужно будет установить pymongo, чтобы правильно запустить его.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
|
import sys import os import pymongo import time import random from datetime import datetime min_date = datetime( 2012 , 1 , 1 ) max_date = datetime( 2013 , 1 , 1 ) delta = (max_date - min_date).total_seconds() job_id = '1' if len(sys.argv) < 2 : sys.exit( "You must supply the item_number argument" ) elif len(sys.argv) > 2 : job_id = sys.argv[ 2 ] documents_number = int (sys.argv[ 1 ]) batch_number = 5 * 1000 ; job_name = 'Job#' + job_id start = datetime.now(); # obtain a mongo connection connection = pymongo.Connection( "mongodb://localhost" , safe=True) # obtain a handle to the random database db = connection.random collection = db.randomData batch_documents = [i for i in range(batch_number)]; for index in range(documents_number): try : date = datetime.fromtimestamp(time.mktime(min_date.timetuple()) + int (round(random.random() * delta))) value = random.random() document = { 'created_on' : date, 'value' : value, } batch_documents[index % batch_number] = document if (index + 1 ) % batch_number == 0 : collection.insert(batch_documents) index += 1 ; if index % 100000 == 0 : print job_name, ' inserted ' , index, ' documents.' except: print 'Unexpected error:' , sys.exc_info()[ 0 ], ', for index ' , index raise print job_name, ' inserted ' , documents_number, ' in ' , (datetime.now() - start).total_seconds(), 's' |
Мы запускаем его, и вот что мы получили на этот раз:
1
2
3
4
5
6
7
|
python create_random.py 50000000 Job #1 inserted 100000 documents. Job #1 inserted 200000 documents. Job #1 inserted 300000 documents. ... Job #1 inserted 49900000 documents. Job #1 inserted 50000000 in 1713.501 s |
Это медленнее по сравнению с версией JavaScript (29180 вставок в секунду), но не стоит расстраиваться. Python — это полнофункциональный язык программирования, так что как насчет использования всех наших ядер ЦП (например, 4 ядра) и запуска по одному сценарию на ядро, каждое из которых вставляет часть общего числа документов (например, 12500000).
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
import sys import pymongo import time import subprocess import multiprocessing from datetime import datetime cpu_count = multiprocessing.cpu_count() # obtain a mongo connection connection = pymongo.Connection( 'mongodb://localhost' , safe=True) # obtain a handle to the random database db = connection.random collection = db.randomData total_documents_count = 50 * 1000 * 1000 ; inserted_documents_count = 0 sleep_seconds = 1 sleep_count = 0 for i in range(cpu_count): documents_number = str(total_documents_count/cpu_count) print documents_number subprocess.Popen([ 'python' , '../create_random.py' , documents_number, str(i)]) start = datetime.now(); while (inserted_documents_count < total_documents_count) is True: inserted_documents_count = collection.count() if (sleep_count > 0 and sleep_count % 60 == 0 ): print 'Inserted ' , inserted_documents_count, ' documents.' if (inserted_documents_count < total_documents_count): sleep_count += 1 time.sleep(sleep_seconds) print 'Inserting ' , total_documents_count, ' took ' , (datetime.now() - start).total_seconds(), 's' |
Запуск сценария Python для параллельного выполнения выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
python create_random_parallel.py Job #3 inserted 100000 documents. Job #2 inserted 100000 documents. Job #0 inserted 100000 documents. Job #1 inserted 100000 documents. Job #3 inserted 200000 documents. ... Job #2 inserted 12500000 in 571.819 s Job #0 inserted 12400000 documents. Job #3 inserted 10800000 documents. Job #1 inserted 12400000 documents. Job #0 inserted 12500000 documents. Job #0 inserted 12500000 in 577.061 s Job #3 inserted 10900000 documents. Job #1 inserted 12500000 documents. Job #1 inserted 12500000 in 578.427 s Job #3 inserted 11000000 documents. ... Job #3 inserted 12500000 in 623.999 s Inserting 50000000 took 624.655 s |
Это действительно очень хорошо (80044 вставок / секунд), даже если оно все еще медленнее, чем первый импорт JavaScript. Итак, давайте адаптируем этот последний скрипт Python для запуска JavaScript через несколько оболочек MongoDB.
Поскольку я не смог предоставить необходимые аргументы для команды mongo, для подпроцесса, запущенного основным скриптом python, я предложил следующую альтернативу:
1
2
3
4
5
6
7
|
for i in range(cpu_count): documents_number = str(total_documents_count/cpu_count) script_name = 'create_random_' + str(i + 1 ) + '.bat' script_file = open(script_name, 'w' ) script_file.write( 'mongo random --eval "var arg1=' + documents_number + ';arg2=' + str(i + 1 ) + '" ../create_random.js' ); script_file.close() subprocess.Popen(script_name) |
Мы генерируем сценарии оболочки динамически и позволяем python запускать их для нас.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
Job #1 inserted 100000 documents. Job #4 inserted 100000 documents. Job #3 inserted 100000 documents. Job #2 inserted 100000 documents. Job #1 inserted 200000 documents. ... Job #4 inserted 12500000 in 566.438s Job #3 inserted 12300000 documents. Job #2 inserted 10800000 documents. Job #1 inserted 11600000 documents. Job #3 inserted 12400000 documents. Job #1 inserted 11700000 documents. Job #2 inserted 10900000 documents. Job #1 inserted 11800000 documents. Job #3 inserted 12500000 documents. Job #3 inserted 12500000 in 574.782s Job #2 inserted 11000000 documents. Job #1 inserted 11900000 documents. Job #2 inserted 11100000 documents. Job #1 inserted 12000000 documents. Job #2 inserted 11200000 documents. Job #1 inserted 12100000 documents. Job #2 inserted 11300000 documents. Job #1 inserted 12200000 documents. Job #2 inserted 11400000 documents. Job #1 inserted 12300000 documents. Job #2 inserted 11500000 documents. Job #1 inserted 12400000 documents. Job #2 inserted 11600000 documents. Job #1 inserted 12500000 documents. Job #1 inserted 12500000 in 591.073s Job #2 inserted 11700000 documents. ... Job #2 inserted 12500000 in 599.005s Inserting 50000000 took 599.253 s |
Это тоже быстро (83437 вставок в секунду), но все еще не может побить нашу первую попытку.
Вывод
Конфигурация моего ПК не является чем-то необычным, и единственная оптимизация заключается в том, что у меня есть SSD-диск, на котором работает MongoDB.
Первая попытка дала наилучшие результаты, и, отслеживая ресурсы ЦП, я понял, что MongoDB использует их все даже для одной консоли оболочки. Скрипт Python, работающий на всех ядрах, также был достаточно быстрым, и у него есть преимущество, позволяющее нам превратить этот скрипт в полностью работающее приложение, если мы хотим.
- Код доступен на GitHub .