Kết nối cơ sở dữ liệu và đa xử lý Django


83

Lý lịch:

Tôi đang làm một dự án sử dụng Django với cơ sở dữ liệu Postgres. Chúng tôi cũng đang sử dụng mod_wsgi trong trường hợp quan trọng, vì một số tìm kiếm trên web của tôi đã đề cập đến nó. Khi gửi biểu mẫu web, chế độ xem Django bắt đầu một công việc sẽ mất một lượng thời gian đáng kể (nhiều hơn người dùng muốn đợi), vì vậy chúng tôi bắt đầu công việc thông qua lệnh gọi hệ thống trong nền. Công việc hiện đang chạy cần có khả năng đọc và ghi vào cơ sở dữ liệu. Vì công việc này mất quá nhiều thời gian nên chúng tôi sử dụng đa xử lý để chạy song song các phần của nó.

Vấn đề:

Tập lệnh cấp cao nhất có kết nối cơ sở dữ liệu và khi nó tạo ra các quy trình con, có vẻ như kết nối của cha mẹ có sẵn cho các con. Sau đó, có một ngoại lệ về cách SET TRANSACTION ISOLATION LEVEL phải được gọi trước một truy vấn. Nghiên cứu đã chỉ ra rằng điều này là do cố gắng sử dụng cùng một kết nối cơ sở dữ liệu trong nhiều quy trình. Một luồng tôi tìm thấy đã đề xuất gọi connect.close () khi bắt đầu các quy trình con để Django sẽ tự động tạo kết nối mới khi nó cần và do đó mỗi quy trình con sẽ có một kết nối duy nhất - tức là không được chia sẻ. Điều này không hiệu quả đối với tôi, vì việc gọi connect.close () trong tiến trình con khiến quy trình mẹ phàn nàn rằng kết nối bị mất.

Những phát hiện khác:

Một số nội dung tôi đã đọc dường như chỉ ra rằng bạn thực sự không thể làm điều này và đa xử lý, mod_wsgi và Django không hoạt động tốt với nhau. Tôi đoán điều đó có vẻ khó tin.

Một số đề xuất sử dụng cần tây, đây có thể là một giải pháp lâu dài, nhưng tôi không thể cài đặt cần tây vào lúc này, đang chờ một số quy trình phê duyệt, vì vậy không phải là một tùy chọn ngay bây giờ.

Tìm thấy một số tài liệu tham khảo trên SO và những nơi khác về các kết nối cơ sở dữ liệu liên tục, mà tôi tin rằng đó là một vấn đề khác.

Cũng tìm thấy các tham chiếu đến psycopg2.pool và pgpool và một cái gì đó về bouncer. Phải thừa nhận rằng tôi không hiểu hầu hết những gì tôi đang đọc trên đó, nhưng nó chắc chắn không làm tôi ngạc nhiên như những gì tôi đang tìm kiếm.

"Công việc xung quanh" hiện tại:

Hiện tại, tôi đã hoàn nguyên để chỉ chạy mọi thứ theo thứ tự và nó hoạt động, nhưng chậm hơn tôi muốn.

Bất kỳ đề xuất nào về cách tôi có thể sử dụng đa xử lý để chạy song song? Có vẻ như nếu tôi có thể để cha mẹ và hai đứa trẻ đều có kết nối độc lập với cơ sở dữ liệu, mọi thứ sẽ ổn, nhưng tôi dường như không thể có được hành vi đó.

Cảm ơn, và xin lỗi vì độ dài!

Câu trả lời:


70

Đa xử lý sao chép các đối tượng kết nối giữa các quy trình vì nó phân tách các quy trình và do đó sao chép tất cả các bộ mô tả tệp của quy trình mẹ. Điều đó đang được nói, một kết nối đến máy chủ SQL chỉ là một tệp, bạn có thể thấy nó trong linux theo / proc // fd / .... bất kỳ tệp nào đang mở sẽ được chia sẻ giữa các tiến trình được phân nhánh. Bạn có thể tìm thêm về rèn tại đây .

Giải pháp của tôi chỉ đơn giản là đóng kết nối db ngay trước khi khởi chạy các quy trình, mỗi quy trình sẽ tự tạo lại kết nối khi nó cần một (được thử nghiệm trong django 1.4):

from django import db
db.connections.close_all()
def db_worker():      
    some_paralell_code()
Process(target = db_worker,args = ())

Pgbouncer / pgpool không được kết nối với các luồng theo nghĩa đa xử lý. Đó là giải pháp tốt hơn cho việc không đóng kết nối theo từng yêu cầu = tăng tốc độ kết nối với các postgres khi chịu tải cao.

Cập nhật:

Để loại bỏ hoàn toàn các vấn đề với kết nối cơ sở dữ liệu, chỉ cần di chuyển tất cả logic được kết nối với cơ sở dữ liệu sang db_worker - Tôi muốn chuyển QueryDict làm đối số ... Ý tưởng tốt hơn chỉ đơn giản là chuyển danh sách id ... Xem QueryDict và values_list ('id', flat = True), và đừng quên chuyển nó vào danh sách! danh sách (QueryDict) trước khi chuyển đến db_worker. Nhờ đó chúng tôi không sao chép kết nối cơ sở dữ liệu mô hình.

def db_worker(models_ids):        
    obj = PartModelWorkerClass(model_ids) # here You do Model.objects.filter(id__in = model_ids)
    obj.run()


model_ids = Model.objects.all().values_list('id', flat=True)
model_ids = list(model_ids) # cast to list
process_count = 5
delta = (len(model_ids) / process_count) + 1

# do all the db stuff here ...

# here you can close db connection
from django import db
db.connections.close_all()

for it in range(0:process_count):
    Process(target = db_worker,args = (model_ids[it*delta:(it+1)*delta]))   

bạn có thể giải thích một chút về việc chuyển ID từ một bộ truy vấn thành một câu hỏi tự trả lời không?
Jharwood

1
multiprocessing sao chép các đối tượng kết nối giữa các quy trình vì nó phân tách các quy trình và do đó sao chép tất cả các bộ mô tả tệp của quy trình mẹ. Điều đó đang được nói, một kết nối đến máy chủ mysql chỉ là một tệp, bạn có thể thấy nó trong linux theo / proc / <PID> / fd / .... bất kỳ tệp nào đang mở sẽ được chia sẻ giữa các quy trình phân nhánh AFAIK. stackoverflow.com/questions/4277289/…
vlad-ardelean,

1
Điều đó có áp dụng cho các chủ đề không? Ví dụ. đóng db conn trong luồng chính, sau đó truy cập db trong mỗi luồng, mỗi luồng sẽ nhận được kết nối riêng của nó?
James Lin

1
Bạn nên sử dụng django.db.connections.close_all()để đóng tất cả các kết nối bằng một cuộc gọi.
Denis Malinovsky

1
Hm ... Đây là cuộc nói chuyện khá thú vị giữa những người từ django: code.djangoproject.com/ticket/20562 có lẽ nó sẽ làm sáng tỏ về chủ đề này? Về cơ bản, các kết nối 'không thể phân nhánh được' ... Mỗi quá trình nên có kết nối riêng.
lechup

18

Khi sử dụng nhiều cơ sở dữ liệu, bạn nên đóng tất cả các kết nối.

from django import db
for connection_name in db.connections.databases:
    db.connections[connection_name].close()

BIÊN TẬP

Vui lòng sử dụng giống như @lechup đã đề cập để đóng tất cả các kết nối (không chắc chắn phương thức này đã được thêm vào phiên bản django nào):

from django import db
db.connections.close_all()

9
đây chỉ là gọi db.close_connection nhiều lần
ibz

2
Tôi không hiểu cách này có thể hoạt động như thế nào nếu không sử dụng bí danh hoặc thông tin ở bất kỳ đâu.
RemcoGerlich

Điều này ... không thể hoạt động. @Mounir, bạn nên sửa đổi nó để sử dụng aliashoặc infotrong nội dung forvòng lặp, nếu dbhoặc close_connection()hỗ trợ điều đó.
0atman

5

Đối với Python 3 và Django 1.9, đây là những gì phù hợp với tôi:

import multiprocessing
import django
django.setup() # Must call setup

def db_worker():
    for name, info in django.db.connections.databases.items(): # Close the DB connections
        django.db.connection.close()
    # Execute parallel code here

if __name__ == '__main__':
    multiprocessing.Process(target=db_worker)

Lưu ý rằng nếu không có django.setup (), tôi không thể làm việc này. Tôi đoán một cái gì đó cần được khởi tạo lại cho quá trình đa xử lý.


Cảm ơn! Điều này đã làm việc cho tôi và có lẽ nên là câu trả lời được chấp nhận ngay bây giờ cho các phiên bản mới hơn của django.
krischan

Cách django là tạo lệnh quản lý chứ không phải tạo tập lệnh trình bao bọc độc lập. Nếu bạn không sử dụng lệnh quản lý Bạn cần sử dụng setupdjango.
lechup 30/07/18

2
Vòng lặp for của bạn không thực sự làm gì cả db.connections.databases.items()- nó chỉ đóng kết nối vài lần. db.connections.close_all()hoạt động tốt miễn là nó được gọi là hàm worker.
tao_oat

2

Tôi đã gặp sự cố "kết nối đóng" khi chạy các trường hợp thử nghiệm Django tuần tự. Ngoài các bài kiểm tra, cũng có một quá trình khác cố ý sửa đổi cơ sở dữ liệu trong quá trình thực hiện kiểm tra. Quá trình này được bắt đầu trong mỗi trường hợp thử nghiệm setUp ().

Một sửa chữa đơn giản là kế thừa các lớp thử nghiệm của tôi TransactionTestCasethay vì TestCase. Điều này đảm bảo rằng cơ sở dữ liệu đã được viết thực sự và quy trình khác có chế độ xem cập nhật về dữ liệu.


1

(không phải là một giải pháp tuyệt vời, nhưng là một giải pháp khả thi)

Nếu bạn không thể sử dụng cần tây, có thể bạn có thể triển khai hệ thống xếp hàng của riêng mình, về cơ bản là thêm các nhiệm vụ vào một số bảng nhiệm vụ và có một cron thường xuyên chọn chúng và xử lý? (thông qua một lệnh quản lý)


có thể - đã hy vọng tránh được mức độ phức tạp đó, nhưng nếu đó là giải pháp duy nhất, thì tôi có thể phải đi theo con đường đó - cảm ơn vì gợi ý. Cần tây có phải là câu trả lời tốt nhất? nếu vậy, tôi có thể cố gắng để có được nó, nhưng sẽ mất một lúc. Tôi cần tây kết hợp với xử lý phân tán như trái ngược với xử lý song song trên một máy, nhưng có lẽ đó chỉ là của tôi thiếu kinh nghiệm với nó ..
daroo

2
cần tây rất phù hợp cho bất kỳ quá trình xử lý nào được yêu cầu ngoài chu kỳ yêu cầu-phản hồi
thứ hai

1

Xin chào, tôi đã gặp sự cố này và có thể giải quyết nó bằng cách thực hiện như sau (chúng tôi đang triển khai một hệ thống tác vụ hạn chế)

task.py

from django.db import connection

def as_task(fn):
    """  this is a decorator that handles task duties, like setting up loggers, reporting on status...etc """ 
    connection.close()  #  this is where i kill the database connection VERY IMPORTANT
    # This will force django to open a new unique connection, since on linux at least
    # Connections do not fare well when forked 
    #...etc

Đã lên lịchJob.py

from django.db import connection

def run_task(request, job_id):
    """ Just a simple view that when hit with a specific job id kicks of said job """ 
    # your logic goes here
    # ...
    processor = multiprocessing.Queue()
    multiprocessing.Process(
        target=call_command,  # all of our tasks are setup as management commands in django
        args=[
            job_info.management_command,
        ],
        kwargs= {
            'web_processor': processor,
        }.items() + vars(options).items()).start()

result = processor.get(timeout=10)  # wait to get a response on a successful init
# Result is a tuple of [TRUE|FALSE,<ErrorMessage>]
if not result[0]:
    raise Exception(result[1])
else:
   # THE VERY VERY IMPORTANT PART HERE, notice that up to this point we haven't touched the db again, but now we absolutely have to call connection.close()
   connection.close()
   # we do some database accessing here to get the most recently updated job id in the database

Thành thật mà nói, để ngăn chặn các điều kiện đua (với nhiều người dùng đồng thời), tốt nhất là gọi database.close () càng nhanh càng tốt sau khi bạn fork quá trình. Tuy nhiên, vẫn có thể có một người dùng khác ở đâu đó hoàn toàn thực hiện yêu cầu tới db trước khi bạn có cơ hội xóa cơ sở dữ liệu.

Thành thật mà nói, sẽ an toàn hơn và thông minh hơn nếu fork của bạn không gọi lệnh trực tiếp, mà thay vào đó gọi một tập lệnh trên hệ điều hành để tác vụ được tạo ra chạy trong trình bao django của chính nó!


Tôi đã sử dụng ý tưởng của bạn về việc đóng cửa bên trong ngã ba thay vì trước đây, để tạo một bộ trang trí mà tôi thêm vào các chức năng công nhân của mình.
Rebs

1

Bạn có thể cung cấp thêm tài nguyên cho Postgre, trong Debian / Ubuntu, bạn có thể chỉnh sửa:

nano /etc/postgresql/9.4/main/postgresql.conf

bằng cách thay thế 9.4 bằng phiên bản postgre của bạn.

Dưới đây là một số dòng hữu ích cần được cập nhật với các giá trị mẫu để làm như vậy, tên tự nói lên:

max_connections=100
shared_buffers = 3000MB
temp_buffers = 800MB
effective_io_concurrency = 300
max_worker_processes = 80

Hãy cẩn thận không tăng cường quá nhiều các thông số này vì nó có thể dẫn đến lỗi khi Postgre cố gắng lấy nhiều nguồn ressource hơn khả dụng. Các ví dụ trên đang chạy tốt trên máy Debian 8GB Ram được trang bị 4 lõi.


0

Nếu tất cả những gì bạn cần là song song I / O và không xử lý song song, bạn có thể tránh vấn đề này bằng cách chuyển các quy trình của mình thành luồng. Thay thế

from multiprocessing import Process

với

from threading import Thread

Đối Threadtượng có giao diện giống nhưProcsess


0

Nếu bạn cũng đang sử dụng gộp kết nối, thì cách sau đây phù hợp với chúng tôi, buộc đóng các kết nối sau khi được chia nhỏ. Trước đây dường như không giúp được gì.

from django.db import connections
from django.db.utils import DEFAULT_DB_ALIAS

connections[DEFAULT_DB_ALIAS].dispose()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.