При экспериментировании с некоторыми коллекциями временных рядов мне понадобился большой набор данных, чтобы убедиться, что наши запросы агрегации не становятся узким местом в случае увеличения загрузки данных. Мы согласились на 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_idvar 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.jsJob#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 sysimport osimport pymongoimport timeimport randomfrom datetime import datetimemin_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_idstart = datetime.now();# obtain a mongo connectionconnection = pymongo.Connection("mongodb://localhost", safe=True)# obtain a handle to the random databasedb = connection.randomcollection = db.randomDatabatch_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 raiseprint job_name, ' inserted ', documents_number, ' in ', (datetime.now() - start).total_seconds(), 's' |
Мы запускаем его, и вот что мы получили на этот раз:
|
1
2
3
4
5
6
7
|
python create_random.py 50000000Job#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 sysimport pymongoimport timeimport subprocessimport multiprocessingfrom datetime import datetimecpu_count = multiprocessing.cpu_count()# obtain a mongo connectionconnection = pymongo.Connection('mongodb://localhost', safe=True)# obtain a handle to the random databasedb = connection.randomcollection = db.randomDatatotal_documents_count = 50 * 1000 * 1000;inserted_documents_count = 0sleep_seconds = 1sleep_count = 0for 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.pyJob#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 sJob#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 sJob#3 inserted 10900000 documents.Job#1 inserted 12500000 documents.Job#1 inserted 12500000 in 578.427 sJob#3 inserted 11000000 documents....Job#3 inserted 12500000 in 623.999 sInserting 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.438sJob#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.782sJob#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.073sJob#2 inserted 11700000 documents....Job#2 inserted 12500000 in 599.005sInserting 50000000 took 599.253 s |
Это тоже быстро (83437 вставок в секунду), но все еще не может побить нашу первую попытку.
Вывод
Конфигурация моего ПК не является чем-то необычным, и единственная оптимизация заключается в том, что у меня есть SSD-диск, на котором работает MongoDB.
Первая попытка дала наилучшие результаты, и, отслеживая ресурсы ЦП, я понял, что MongoDB использует их все даже для одной консоли оболочки. Скрипт Python, работающий на всех ядрах, также был достаточно быстрым, и у него есть преимущество, позволяющее нам превратить этот скрипт в полностью работающее приложение, если мы хотим.
- Код доступен на GitHub .
