Ваші цілі:
- Поширюйте свою роботу на багатьох машинах (розподілені обчислення / розподілена паралельна обробка)
- Розподіліть роботу на певній машині між усіма процесорами (багатопроцесорні / потокові)
Обидва ці селери можна зробити для вас досить легко. Перше, що слід зрозуміти, це те, що кожен працівник селери за замовчуванням налаштований на те, щоб виконувати стільки завдань, скільки ядер центрального процесора доступно в системі:
Паралельність - це кількість попередніх робочих процесів, що використовуються для одночасної обробки ваших завдань, коли всі вони зайняті роботою, новим завданням доведеться почекати, поки одне із завдань закінчиться, перш ніж його можна буде обробити.
Номер паралельності за замовчуванням - це кількість процесорів на цій машині (включаючи ядра) , ви можете вказати власний номер, використовуючи опцію -c. Немає рекомендованого значення, оскільки оптимальна кількість залежить від ряду факторів, але якщо ваші завдання в основному пов'язані з введенням / виводом, тоді ви можете спробувати збільшити його, експерименти показали, що додавання більш ніж подвоєного числа процесорів рідко буває рідко ефективний, і натомість може погіршити продуктивність.
Це означає, що кожному окремому завданню не потрібно турбуватися про використання багатопроцесорної / потокової роботи для використання декількох процесорів / ядер. Натомість селера одночасно буде виконувати достатньо завдань, щоб використовувати кожен доступний процесор.
Поки це не буде зроблено, наступним кроком є створення завдання, яке обробляє обробку деякої підмножини вашого list_of_millions_of_ids
. Тут у вас є кілька варіантів - один полягає в тому, щоб кожне завдання обробляло один ідентифікатор, тому ви виконуєте N завдань, де N == len(list_of_millions_of_ids)
. Це гарантуватиме рівномірний розподіл роботи між усіма вашими завданнями, оскільки ніколи не буде випадку, щоб один робітник закінчив достроково і просто чекав; якщо йому потрібна робота, він може витягнути ідентифікатор із черги. Ви можете зробити це (як згадував Джон Доу), використовуючи селеруgroup
.
tasks.py:
@app.task
def process_id(item):
id = item
database.objects(newid=id).save()
І для виконання завдань:
from celery import group
from tasks import process_id
jobs = group(process_id.s(item) for item in list_of_millions_of_ids)
result = jobs.apply_async()
Інший варіант - розбити список на більш дрібні шматочки та розподілити їх своїм працівникам. Цей підхід ризикує витратити деякі цикли, тому що у вас можуть опинитися деякі працівники, які чекають, поки інші все ще виконують роботу. Однак документація селери зазначає, що це занепокоєння часто є необгрунтованим:
Деякі можуть хвилюватися, що відбивання ваших завдань призводить до погіршення паралелізму, але це рідко стосується зайнятого кластера, і на практиці, оскільки ви уникаєте накладних витрат на обмін повідомленнями, це може значно підвищити продуктивність.
Отже, ви можете виявити, що обробляти список та розподіляти фрагменти для кожного завдання працює ефективніше через зменшення накладних витрат на обмін повідомленнями. Можливо, ви також можете трохи полегшити навантаження на базу даних таким чином, обчислюючи кожен ідентифікатор, зберігаючи його в списку, а потім додаючи весь список до БД, коли закінчите, а не робити це один ідентифікатор за раз . Підступний підхід виглядав би приблизно так
tasks.py:
@app.task
def process_ids(items):
for item in items:
id = item
database.objects(newid=id).save()
І для початку завдань:
from tasks import process_ids
jobs = process_ids.chunks(list_of_millions_of_ids, 30)
jobs.apply_async()
Ви можете трохи поекспериментувати з тим, який розмір поршень дає найкращий результат. Ви хочете знайти солодке місце, де ви скорочуєте обмін повідомленнями, одночасно зберігаючи розмір достатньо малим, щоб у вас не вийшло, що працівники закінчували б шматок набагато швидше, ніж інший робітник, а потім просто чекали, нічого робити.