PostgreSQL
 sql >> Cơ Sở Dữ Liệu >  >> RDS >> PostgreSQL

Hiệu suất ứng dụng dựa trên PostgreSQL:độ trễ và độ trễ ẩn

Goldfields Pipeline, của SeanMac (Wikimedia Commons)

Nếu bạn đang cố gắng tối ưu hóa hiệu suất của ứng dụng dựa trên PostgreSQL của mình, bạn có thể đang tập trung vào các công cụ thông thường: GIẢI THÍCH (BUFFERS, ANALYZE) , pg_stat_statements , auto_explain , log_statement_min_duration , v.v.

Có thể bạn đang xem xét sự cạnh tranh về khóa với log_lock_waits , giám sát hiệu suất trạm kiểm soát của bạn, v.v.

Nhưng bạn có nghĩ đến độ trễ mạng ? Người chơi biết về độ trễ của mạng, nhưng bạn có nghĩ rằng nó quan trọng đối với máy chủ ứng dụng của bạn không?

Vấn đề về độ trễ

Độ trễ mạng khứ hồi của máy khách / máy chủ điển hình có thể dao động từ 0,01ms (máy chủ cục bộ) thông qua ~ 0,5ms của mạng chuyển mạch, 5ms của WiFi, 20ms của ADSL, 300ms của định tuyến liên lục địa và hơn thế nữa đối với những thứ như liên kết vệ tinh và WWAN .

Một SELECT tầm thường có thể mất theo thứ tự 0,1ms để thực thi phía máy chủ. Một INSERT tầm thường có thể mất 0,5 mili giây.

Mỗi khi ứng dụng của bạn chạy một truy vấn, nó phải đợi máy chủ phản hồi thành công / thất bại và có thể là một tập hợp kết quả, siêu dữ liệu truy vấn, v.v. Điều này gây ra sự chậm trễ ít nhất một vòng mạng.

Khi bạn đang làm việc với các truy vấn nhỏ, độ trễ mạng đơn giản có thể đáng kể so với thời gian thực thi các truy vấn của bạn nếu cơ sở dữ liệu của bạn không nằm trên cùng một máy chủ lưu trữ như ứng dụng của bạn.

Nhiều ứng dụng, đặc biệt là ORM, rất dễ bị chạy nhiều của các truy vấn khá đơn giản. Ví dụ:nếu ứng dụng Hibernate của bạn đang tìm nạp một thực thể có @OneToMany được tìm nạp chậm mối quan hệ với 1000 mục con, nó có thể sẽ thực hiện 1001 truy vấn nhờ vào vấn đề chọn n + 1, nếu không muốn nói nhiều hơn. Điều đó có nghĩa là nó có thể tiêu tốn gấp 1000 lần độ trễ cho chuyến đi vòng quanh mạng của bạn chỉ để chờ đợi . Bạn có thể tìm nạp tham gia bên trái để tránh điều đó… nhưng sau đó bạn chuyển thực thể mẹ 1000 lần trong phép tham gia và phải loại bỏ trùng lặp.

Tương tự, nếu bạn đang điền cơ sở dữ liệu từ ORM, bạn có thể đang thực hiện hàng trăm nghìn INSERT tầm thường s… và đợi sau mỗi cái để máy chủ xác nhận là OK.

Thật dễ dàng để cố gắng tập trung vào thời gian thực thi truy vấn và cố gắng tối ưu hóa điều đó, nhưng bạn chỉ có thể làm được nhiều điều với CHÈN VÀO ... GIÁ TRỊ ... tầm thường. . Bỏ một số chỉ mục và ràng buộc, đảm bảo rằng nó được đưa vào một giao dịch và bạn đã hoàn thành khá nhiều việc.

Điều gì về việc loại bỏ tất cả các mạng chờ đợi? Ngay cả trong một mạng LAN, họ bắt đầu thêm hàng nghìn truy vấn.

BẢN SAO

Một cách để tránh độ trễ là sử dụng COPY . Để sử dụng hỗ trợ COPY của PostgreSQL, ứng dụng hoặc trình điều khiển của bạn phải tạo một tập hợp các hàng giống CSV và truyền chúng đến máy chủ theo một trình tự liên tục. Hoặc máy chủ có thể được yêu cầu gửi ứng dụng của bạn một luồng giống như CSV.

Dù bằng cách nào, ứng dụng không được xen kẽ BẢN SAO với các truy vấn khác và bản sao chèn phải được tải trực tiếp vào bảng đích. Cách tiếp cận phổ biến là COPY vào một bảng tạm thời, sau đó từ đó thực hiện CHÈN VÀO ... CHỌN ... , CẬP NHẬT ... TỪ .... , XÓA TỪ ... ĐANG SỬ DỤNG ... , v.v. để sử dụng dữ liệu đã sao chép để sửa đổi các bảng chính trong một thao tác duy nhất.

Điều đó rất hữu ích nếu bạn đang viết trực tiếp SQL của riêng mình, nhưng nhiều khung ứng dụng và ORM không hỗ trợ nó, ngoài ra nó chỉ có thể thay thế trực tiếp INSERT đơn giản . Ứng dụng, khuôn khổ hoặc trình điều khiển ứng dụng khách của bạn phải xử lý chuyển đổi cho bản đại diện đặc biệt mà COPY cần , tự tra cứu bất kỳ siêu dữ liệu loại bắt buộc nào, v.v.

(Trình điều khiển đáng chú ý mà làm hỗ trợ COPY bao gồm libpq, PgJDBC, psycopg2 và Pg gem… nhưng không nhất thiết phải là các khuôn khổ và ORM được xây dựng trên chúng.)

PgJDBC - chế độ hàng loạt

Trình điều khiển JDBC của PostgreSQL có giải pháp cho vấn đề này. Nó dựa vào sự hỗ trợ hiện có trong các máy chủ PostgreSQL kể từ ngày 8.4 và dựa trên các tính năng theo lô của API JDBC để gửi một truy vấn đến máy chủ, sau đó chỉ đợi một lần để xác nhận rằng toàn bộ lô chạy OK.

Về lý thuyết. Trong thực tế, một số thách thức triển khai hạn chế điều này để các lô chỉ có thể được thực hiện với số lượng ít nhất là vài trăm truy vấn. Trình điều khiển cũng chỉ có thể chạy các truy vấn trả về các hàng kết quả theo từng đợt nếu nó có thể tìm ra kết quả sẽ lớn như thế nào trước thời hạn. Bất chấp những hạn chế đó, hãy sử dụng Statement.executeBatch () có thể tăng hiệu suất rất lớn cho các ứng dụng đang thực hiện các tác vụ như tải dữ liệu hàng loạt các phiên bản cơ sở dữ liệu từ xa.

Bởi vì nó là một API tiêu chuẩn, nó có thể được sử dụng bởi các ứng dụng hoạt động trên nhiều công cụ cơ sở dữ liệu. Ví dụ:Hibernate có thể sử dụng phân phối JDBC mặc dù nó không làm như vậy theo mặc định.

libpq và theo lô

Hầu hết (tất cả?) Các trình điều khiển PostgreSQL khác không có hỗ trợ cho việc phân lô. PgJDBC triển khai giao thức PostgreSQL hoàn toàn độc lập, khi hầu hết các trình điều khiển khác sử dụng nội bộ thư viện C libpq được cung cấp như một phần của PostgreSQL.

libpq không hỗ trợ phân lô. Nó có một API không chặn không đồng bộ, nhưng khách hàng vẫn chỉ có thể có một truy vấn “đang bay” tại một thời điểm. Nó phải đợi cho đến khi nhận được kết quả của truy vấn đó trước khi có thể gửi một truy vấn khác.

PostgreSQL máy chủ hỗ trợ phân lô tốt và PgJDBC đã sử dụng nó. Vì vậy, tôi đã viết hỗ trợ hàng loạt cho libpq và gửi nó như một ứng cử viên cho phiên bản PostgreSQL tiếp theo. Vì nó chỉ thay đổi máy khách, nếu được chấp nhận, nó sẽ vẫn tăng tốc khi kết nối với các máy chủ cũ hơn.

Tôi thực sự quan tâm đến phản hồi từ các tác giả và người dùng nâng cao của libpq trình điều khiển và nhà phát triển dựa trên khách hàng của libpq ứng dụng dựa trên. Bản vá áp dụng tốt trên PostgreSQL 9.6beta1 nếu bạn muốn dùng thử. Tài liệu chi tiết và có một chương trình ví dụ toàn diện.

Hiệu suất

Tôi nghĩ rằng một dịch vụ cơ sở dữ liệu được lưu trữ như RDS hoặc Heroku Postgres sẽ là một ví dụ điển hình về nơi loại chức năng này sẽ hữu ích. Đặc biệt, việc truy cập chúng từ mạng của chúng ta bên cạnh mạng của chính chúng thực sự cho thấy độ trễ có thể ảnh hưởng đến mức nào.

Độ trễ mạng ~ 320ms:

  • 500 lần chèn không phân lô: 167,0 giây
  • 500 lần chèn với hàng loạt: 1,2 giây

… Nhanh hơn 120 lần.

Bạn thường sẽ không chạy ứng dụng của mình qua liên kết liên lục địa giữa máy chủ ứng dụng và cơ sở dữ liệu, nhưng điều này làm nổi bật tác động của độ trễ. Ngay cả khi qua một ổ cắm unix tới máy chủ cục bộ, tôi đã thấy cải thiện hiệu suất hơn 50% cho 10000 lần chèn.

Kết hợp trong các ứng dụng hiện có

Rất tiếc là không thể tự động bật tính năng phân lô cho các ứng dụng hiện có. Các ứng dụng phải sử dụng một giao diện hơi khác, nơi chúng gửi một loạt các truy vấn và chỉ sau đó yêu cầu kết quả.

Sẽ khá đơn giản để điều chỉnh các ứng dụng đã sử dụng giao diện libpq không đồng bộ, đặc biệt nếu chúng sử dụng chế độ không chặn và select () / thăm dò ý kiến ​​() / epoll () / WaitForMultipleObjectsEx vòng. Các ứng dụng sử dụng libpq đồng bộ giao diện sẽ yêu cầu nhiều thay đổi hơn.

Kết hợp trong các trình điều khiển ứng dụng khách khác

Tương tự như vậy, các trình điều khiển máy khách, khuôn khổ và ORM nói chung sẽ cần các thay đổi về giao diện và nội bộ để cho phép sử dụng theo lô. Nếu họ đã sử dụng vòng lặp sự kiện và I / O không chặn, họ sẽ sửa đổi khá đơn giản.

Tôi muốn thấy người dùng Python, Ruby, v.v. có thể truy cập chức năng này, vì vậy tôi rất tò mò muốn biết ai quan tâm. Hãy tưởng tượng bạn có thể làm điều này:

 import psycopg2conn =psycopg2.connect (...) cur =conn.cursor () # đây chỉ là một ý tưởng, mã này không hoạt động với psycopg2:futures =[cur.async_execute (sql) cho sql trong my_queries] cho tương lai trong tương lai:result =future.result # chờ nếu kết quả chưa sẵn sàng ... xử lý kết quả ... conn.commit () 

Thực thi hàng loạt không đồng bộ không cần phải phức tạp ở cấp máy khách.

SAO CHÉP nhanh nhất

Trường hợp khách hàng thực tế vẫn nên ưu tiên COPY . Dưới đây là một số kết quả từ máy tính xách tay của tôi:

 chèn 1000000 hàng theo lô, không theo nhóm và với chèn COPYbatch đã trôi qua:23.715315 

Việc phân phối công việc mang lại hiệu suất lớn đáng ngạc nhiên ngay cả trên kết nối ổ cắm unix cục bộ…. nhưng SAO CHÉP khiến cả hai cách tiếp cận chèn riêng lẻ bị bỏ xa trong bụi.

Sử dụng COPY .

Hình ảnh

Hình ảnh cho bài đăng này là đường ống của Goldfields Water Supply Scheme từ Mundaring Weir gần Perth ở Tây Úc đến các mỏ vàng trong đất liền (sa mạc). Nó có liên quan vì mất quá nhiều thời gian để hoàn thành và bị chỉ trích dữ dội đến mức nhà thiết kế và người đề xuất chính của nó, C. Y. O’Connor, đã tự sát 12 tháng trước khi nó được đưa vào hoạt động. Ở địa phương, mọi người thường (không chính xác) nói rằng anh ấy đã chết sau khi đường ống được xây dựng khi không có nước chảy - bởi vì chỉ mất quá nhiều thời gian mà mọi người đều cho rằng dự án đường ống đã thất bại. Sau đó vài tuần, nước đổ đi.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Nếu số lượng PostgreSQL (*) luôn chậm, làm thế nào để phân trang các truy vấn phức tạp?

  2. Thứ tự kết quả của các mô hình lồng nhau được tải sẵn trong Node Sequelize

  3. Cách tốt nhất để xóa hàng triệu hàng theo ID

  4. Kết nối với Heroku Postgres từ Spring Boot

  5. Thêm cột dấu thời gian với mặc định NOW () chỉ cho các hàng mới