การเพิ่มประสิทธิภาพการเชื่อมต่อ

เสร็จสมบูรณ์เมื่อ

การเชื่อมต่อฐานข้อมูลเป็นทรัพยากรที่มีราคาแพง การเชื่อมต่อแต่ละครั้งจะใช้หน่วยความจําเซิร์ฟเวอร์ ต้องใช้ค่าใช้จ่ายในการรับรองความถูกต้อง และนับรวมกับขีดจํากัดของเซิร์ฟเวอร์ สําหรับแอปพลิเคชัน AI ที่ทําการสืบค้นเวกเตอร์บ่อยครั้ง การจัดการการเชื่อมต่อที่มีประสิทธิภาพเป็นสิ่งสําคัญเพื่อให้ได้ปริมาณงานสูงโดยไม่ทําให้ทรัพยากรหมด

Note

ตัวอย่างโค้ดในหน่วยนี้สาธิตรูปแบบการจัดการการเชื่อมต่อสําหรับ Python (psycopg) และ .NET (Npgsql) ไลบรารีเหล่านี้ได้รับการปรับปรุงบ่อยครั้ง ไปที่ เอกสารประกอบ psycopg และ เอกสารประกอบ Npgsql สําหรับรายละเอียด API ปัจจุบัน

ค่าโสหุ้ยการเชื่อมต่อ

การสร้างการเชื่อมต่อ PostgreSQL ใหม่เกี่ยวข้องกับหลายขั้นตอน โดยแต่ละขั้นตอนจะเพิ่มเวลาแฝง:

  1. การจับมือ TCP: การสร้างการเชื่อมต่อเครือข่าย (โดยทั่วไป 1-3 ไป-กลับ)
  2. การเจรจาต่อรอง TLS: การเข้ารหัสลับการเชื่อมต่อ (จําเป็นสําหรับฐานข้อมูล Azure สําหรับ PostgreSQL)
  3. การรับรองความถูกต้อง: การตรวจสอบข้อมูลประจําตัว (การแลกเปลี่ยนรหัสผ่านหรือโทเค็น)
  4. การจัดสรรกระบวนการเซิร์ฟเวอร์: PostgreSQL สร้างกระบวนการแบ็กเอนด์สําหรับการเชื่อมต่อแต่ละครั้ง
  5. การเริ่มต้นเซสชัน: การตั้งค่าพารามิเตอร์เซสชันและการกําหนดค่าการโหลด

ลําดับนี้ใช้เวลา 50-200 มิลลิวินาที ขึ้นอยู่กับเวลาแฝงของเครือข่ายและโหลดเซิร์ฟเวอร์ สําหรับกลไกการแนะนําที่จัดการคําขอหลายพันรายการต่อวินาที การสร้างการเชื่อมต่อใหม่ต่อคําขอจะใช้เวลาในการตั้งค่าการเชื่อมต่อมากกว่าในการดําเนินการสืบค้นจริง

Azure Database for PostgreSQL จํากัดการเชื่อมต่อพร้อมกันตามระดับการประมวลผล Burstable B1ms อนุญาตให้มีการเชื่อมต่อ 50 รายการ General Purpose 2 vCores รองรับการเชื่อมต่อ 859 รายการ General Purpose 4 vCores รองรับการเชื่อมต่อ 1,718 รายการ Memory Optimized 4 vCores รองรับการเชื่อมต่อ 3,437 รายการ และ Memory Optimized 16 vCores รองรับการเชื่อมต่อ 5,000 รายการ การเกินขีดจํากัดเหล่านี้ทําให้เกิดความล้มเหลวในการเชื่อมต่อ แอปพลิเคชันที่สร้างการเชื่อมต่อต่อคําขอสามารถถึงขีดจํากัดเหล่านี้ได้อย่างรวดเร็วในช่วงที่มีการรับส่งข้อมูลพุ่งสูงขึ้น

การรวมการเชื่อมต่อกับ PgBouncer

PgBouncer เป็นตัวจัดกลุ่มการเชื่อมต่อที่มีน้ําหนักเบาซึ่งอยู่ระหว่างแอปพลิเคชันของคุณและ PostgreSQL รักษาพูลของการเชื่อมต่อฐานข้อมูลและการเชื่อมต่อไคลเอ็นต์แบบมัลติเพล็กซ์ระหว่างพวกเขา ซึ่งช่วยลดจํานวนการเชื่อมต่อฐานข้อมูลจริงที่จําเป็นลงอย่างมาก

Azure Database for PostgreSQL มีการสนับสนุน PgBouncer ในตัวในระดับการประมวลผลสําหรับวัตถุประสงค์ทั่วไปและระดับการประมวลผลที่ปรับให้เหมาะสมกับหน่วยความจํา ระดับ Burstable ไม่รองรับคุณสมบัตินี้ เปิดใช้งาน PgBouncer ผ่านพอร์ทัล Azure หรือ CLI เมื่อเปิดใช้งานแล้ว ให้เชื่อมต่อผ่านพอร์ต 6432 (พอร์ต PgBouncer) แทน 5432 (พอร์ต PostgreSQL โดยตรง) สตริงการเชื่อมต่อ PgBouncer ใช้postgresql://user:password@myserver.postgres.database.azure.com:6432/mydb

az postgres flexible-server parameter set \
    --resource-group myResourceGroup \
    --server-name myserver \
    --name pgbouncer.enabled \
    --value true

PgBouncer รองรับโหมดการรวมสามโหมด โดยแต่ละโหมดมีการแลกเปลี่ยนที่แตกต่างกัน โหมดเซสชันหมายถึงไคลเอ็นต์จะเชื่อมต่อเซิร์ฟเวอร์ตลอดทั้งเซสชัน (จนกว่าจะยกเลิกการเชื่อมต่อ) โหมดนี้รองรับคุณสมบัติ PostgreSQL ทั้งหมด แต่ลดการเชื่อมต่อน้อยที่สุด โหมดธุรกรรมหมายถึงไคลเอ็นต์ถือการเชื่อมต่อเซิร์ฟเวอร์ระหว่างการทําธุรกรรมเท่านั้น ระหว่างธุรกรรม การเชื่อมต่อจะกลับไปยังพูล โหมดนี้ทํางานได้ดีกับแอปพลิเคชันส่วนใหญ่และลดความต้องการการเชื่อมต่อลงอย่างมาก โหมดใบแจ้งยอดหมายความว่าไคลเอ็นต์จะได้รับการเชื่อมต่อสําหรับแต่ละคําสั่งเท่านั้น โหมดนี้ให้การลดการเชื่อมต่อสูงสุด แต่ไม่สนับสนุนธุรกรรมหลายใบแจ้งยอด สําหรับปริมาณงานการค้นหาเวกเตอร์ โหมดธุรกรรมมักเป็นตัวเลือกที่ดีที่สุด

PgBouncer เปิดเผยพารามิเตอร์หลายตัวที่ควบคุมพฤติกรรมของพูล ขีดจํากัดการเชื่อมต่อ และการจัดการการหมดเวลา สําหรับปริมาณงานการค้นหาเวกเตอร์ที่มีรูปแบบการรับส่งข้อมูลต่อเนื่อง การปรับแต่งพารามิเตอร์เหล่านี้จะช่วยสร้างสมดุลระหว่างความพร้อมใช้งานของการเชื่อมต่อกับการใช้ทรัพยากร กําหนดค่า pgbouncer.default_pool_size (20-50 ขึ้นอยู่กับความต้องการพร้อมกัน) pgbouncer.max_client_conn (5000+ สําหรับแอปพลิเคชันที่มีการเข้าชมสูง) pgbouncer.pool_mode (ธุรกรรม) และ pgbouncer.query_wait_timeout (30-120 วินาที)

โหมดธุรกรรมส่งคืนการเชื่อมต่อไปยังพูลหลังจากแต่ละธุรกรรมคอมมิตหรือย้อนกลับ สิ่งนี้ส่งผลต่อคุณสมบัติ PostgreSQL หลายอย่าง ตัวแปรเซสชันจะรีเซ็ตระหว่างธุรกรรม ดังนั้นการตั้งค่าที่ใช้กับ SET จะไม่คงอยู่กับธุรกรรมต่างๆ ใช้ SET LOCAL ภายในธุรกรรมหรือกําหนดค่าเริ่มต้นฝั่งเซิร์ฟเวอร์ ใบแจ้งยอดที่เตรียมไว้อาจไม่ทํางานเนื่องจากใบแจ้งยอดที่เตรียมไว้ที่มีชื่อจะเชื่อมโยงกับการเชื่อมต่อ ในโหมดธุรกรรม ใบแจ้งยอดที่เตรียมไว้ที่สร้างขึ้นในธุรกรรมหนึ่งอาจไม่พร้อมใช้งานในธุรกรรมถัดไปหากมีการกําหนดการเชื่อมต่ออื่น LISTEN/NOTIFY ไม่ทํางานเนื่องจากคุณลักษณะเหล่านี้ต้องการการเชื่อมต่อแบบถาวรและเข้ากันไม่ได้กับการรวมธุรกรรม สําหรับแอปพลิเคชันการค้นหาเวกเตอร์ ข้อจํากัดเหล่านี้ไม่ค่อยมีปัญหา เนื่องจากการสืบค้นมักเป็นการเลือกอย่างง่ายโดยไม่มีสถานะเฉพาะเซสชัน

การรวมการเชื่อมต่อระดับแอปพลิเคชัน

นอกเหนือจาก (หรือแทนที่) PgBouncer แอปพลิเคชันของคุณสามารถจัดการพูลการเชื่อมต่อได้โดยตรง สิ่งนี้ให้การควบคุมวงจรชีวิตการเชื่อมต่อที่ละเอียดยิ่งขึ้นและผสานรวมกับเฟรมเวิร์กแอปพลิเคชัน

แพ็คเกจนี้ psycopg_pool ให้การรวมการเชื่อมต่อสําหรับ psycopg พูลระดับแอปพลิเคชันช่วยให้คุณควบคุมวงจรชีวิตการเชื่อมต่อ พฤติกรรมการหมดเวลาที่ไม่ได้ใช้งาน และการตรวจสอบสถานภาพ นอกจากนี้ยังรวมเข้ากับการจัดการข้อผิดพลาดและการบันทึกของแอปพลิเคชันของคุณอย่างเป็นธรรมชาติ เมื่อรวมกับ PgBouncer พูลแอปพลิเคชันจะจัดการการจัดการการเชื่อมต่อภายในเครื่องในขณะที่ PgBouncer จัดการมัลติเพล็กซ์ฝั่งเซิร์ฟเวอร์ with pool.connection()ตัวจัดการบริบทจะส่งคืนการเชื่อมต่อไปยังพูลโดยอัตโนมัติเมื่อบล็อกออก แม้ว่าจะมีข้อยกเว้นเกิดขึ้นก็ตาม

from psycopg_pool import ConnectionPool

# Create a connection pool
pool = ConnectionPool(
    conninfo="postgresql://user:password@myserver.postgres.database.azure.com:6432/mydb",
    min_size=5,      # Minimum connections to maintain
    max_size=20,     # Maximum connections allowed
    max_idle=300,    # Close idle connections after 5 minutes
    max_lifetime=3600  # Recycle connections after 1 hour
)

# Use connections from the pool
def search_similar_products(query_embedding, limit=10):
    with pool.connection() as conn:
        with conn.cursor() as cur:
            cur.execute("""
                SELECT id, name, embedding <=> %s AS distance
                FROM products
                ORDER BY embedding <=> %s
                LIMIT %s
            """, (query_embedding, query_embedding, limit))
            return cur.fetchall()

Npgsql มีการเชื่อมต่อพูลในตัวที่เปิดใช้งานโดยค่าเริ่มต้น ดังนั้นคุณไม่จําเป็นต้องมีแพ็คเกจแยกต่างหาก พูลจะจัดการการสร้าง นํากลับมาใช้ใหม่ และการกําจัดการเชื่อมต่อโดยอัตโนมัติตามพารามิเตอร์ที่คุณระบุในสตริงการเชื่อมต่อ สตริงการเชื่อมต่อที่ไม่ซ้ํากันแต่ละสตริงจะรักษาพูลของตัวเอง ดังนั้นสตริงการเชื่อมต่อที่สอดคล้องกันทั่วทั้งแอปพลิเคชันของคุณจึงมั่นใจได้ถึงการใช้พูลอย่างมีประสิทธิภาพ เมื่อคุณโทร conn.Close() หรือการเชื่อมต่อถูกกําจัด มันจะกลับไปที่พูลแทนที่จะถูกทําลาย กําหนดค่าการรวมกลุ่มผ่านพารามิเตอร์สตริงการเชื่อมต่อ เช่น Minimum Pool Size=5;Maximum Pool Size=20;Connection Idle Lifetime=300;Connection Lifetime=3600.

ขนาดพูลมีผลต่อทั้งประสิทธิภาพและการใช้ทรัพยากร การตั้งค่าพูลที่เล็กเกินไปจะทําให้คําขอรอการเชื่อมต่อที่พร้อมใช้งาน ซึ่งจะเพิ่มเวลาแฝง การตั้งค่าที่ใหญ่เกินไปจะสิ้นเปลืองหน่วยความจําและอาจทําให้เซิร์ฟเวอร์ฐานข้อมูลล้นหลาม ขนาดที่เหมาะสมขึ้นอยู่กับรูปแบบการรับส่งข้อมูล ระยะเวลาการสืบค้น และจํานวนอินสแตนซ์ของแอปพลิเคชันที่แชร์ฐานข้อมูล รักษาขนาดขั้นต่ําให้ใหญ่พอที่จะรองรับการรับส่งข้อมูลพื้นฐานโดยไม่ต้องรอ จํากัดขนาดสูงสุดที่ฐานข้อมูลสามารถจัดการได้หารด้วยจํานวนอินสแตนซ์ของแอปพลิเคชัน หากคุณมีอินสแตนซ์แอปพลิเคชัน 10 รายการ และฐานข้อมูลของคุณรองรับการเชื่อมต่อ 1,000 รายการ ให้ตั้งค่าสูงสุดเป็น 100 อินสแตนซ์ต่ออินสแตนซ์ (ออกจากพื้นที่ว่าง) รีไซเคิลการเชื่อมต่อเป็นระยะ (ทุกๆ 30-60 นาที) เพื่อรักษาสุขภาพ เนื่องจากการเชื่อมต่อที่มีอายุการใช้งานยาวนานอาจสะสมหน่วยความจํารั่วไหลหรือเก็บแผนแคชเก่าไว้

การจัดการเซสชันสําหรับปริมาณงาน AI

แบบสอบถามแบบเวกเตอร์บางรายการได้รับประโยชน์จากการตั้งค่าระดับเซสชันที่จัดสรรทรัพยากรให้กับแบบสอบถามมากกว่าค่าเริ่มต้นทั่วทั้งเซิร์ฟเวอร์ที่อนุญาต

แบบสอบถามความคล้ายคลึงกันของเวกเตอร์ที่จัดเรียงชุดผลลัพธ์ขนาดใหญ่จะได้รับประโยชน์จาก work_memการเพิ่ม ตั้งค่าสําหรับเซสชันหรือธุรกรรมเฉพาะโดยใช้SET LOCAL work_mem = '256MB' SET LOCAL ใช้ได้เฉพาะภายในธุรกรรมปัจจุบันเท่านั้น เมื่อธุรกรรมสิ้นสุดลง การตั้งค่าจะเปลี่ยนกลับเป็นค่าเริ่มต้น ซึ่งปลอดภัยสําหรับการเชื่อมต่อแบบพูล

ปรับ hnsw.ef_search หรือ ivfflat.probes สําหรับคิวรีที่มีข้อกําหนดด้านความแม่นยําต่างกัน ใช้ SET LOCAL hnsw.ef_search = 200 สําหรับการเรียกคืนที่สูงขึ้นในคิวรีที่ความแม่นยําเป็นสิ่งสําคัญ หรือ SET LOCAL hnsw.ef_search = 20 สําหรับการสืบค้นที่เร็วขึ้นซึ่งยอมรับผลลัพธ์โดยประมาณได้ รูปแบบนี้ช่วยให้คุณสร้างสมดุลระหว่างความแม่นยําและความเร็วตามกรณีการใช้งานเฉพาะโดยไม่ส่งผลกระทบต่อการสืบค้นอื่นๆ

รูปแบบการใช้งาน SDK ที่มีประสิทธิภาพ

นอกเหนือจากการจัดการการเชื่อมต่อแล้ว วิธีที่คุณจัดโครงสร้างการโต้ตอบกับฐานข้อมูลจะส่งผลต่อประสิทธิภาพการทํางาน

การเดินทางไปกลับเครือข่ายจะเพิ่มเวลาแฝงให้กับทุกการดําเนินการฐานข้อมูล เมื่อคุณต้องการข้อมูลหลายชิ้น การดึงข้อมูลในคิวรีเดียวจะช่วยลดค่าใช้จ่ายต่อคิวรีของการส่งผ่านเครือข่าย สําหรับแอปพลิเคชัน AI ที่ดึงข้อมูลการฝังสําหรับหลายรายการ การแบทช์สามารถลดเวลาแฝงทั้งหมดจากหลายร้อยมิลลิวินาทีเหลือตัวเลขหลักเดียว แทนที่จะเดินทางไปกลับหลายครั้งด้วยคิวรีแต่ละรายการ ให้ใช้คิวรีเดียวกับ WHERE id = ANY(%s) และส่งรายการรหัส

สําหรับการโหลดเวกเตอร์จํานวนมากคําสั่ง PostgreSQL COPY จะเร็วกว่าคําสั่งแต่ละ INSERT คําสั่งอย่างมาก COPY สตรีมข้อมูลโดยตรงไปยังตารางในรูปแบบไบนารีหรือข้อความ โดยข้ามค่าใช้จ่ายในการแยกวิเคราะห์คําสั่ง SQL แต่ละคําสั่ง เมื่อโหลดการฝังข้อมูลจากไปป์ไลน์การประมวลผลแบบแบตช์หรือการย้าย COPY ข้อมูลเริ่มต้น สามารถลดเวลาในการโหลดจากชั่วโมงเป็นนาที COPY สามารถโหลดได้หลายแสนแถวต่อวินาที ในขณะที่การแทรกแต่ละรายการถูกจํากัดไว้ที่หลายพันแถวต่อวินาที

เมื่อแอปพลิเคชันของคุณสามารถทํางานแบบขนานได้ การดําเนินการฐานข้อมูลแบบอะซิงโครนัสจะปรับปรุงปริมาณงานโดยดําเนินการสืบค้นหลายรายการพร้อมกันโดยไม่บล็อกเธรด รูปแบบนี้มีประโยชน์สําหรับปริมาณงาน AI ที่ต้องค้นหาคอลเลกชันเวกเตอร์หลายรายการพร้อมกัน หรือรวมการค้นหาเวกเตอร์กับการดึงข้อมูลอื่นๆ พูลแบบอะซิงโครนัสจัดการการเชื่อมต่ออย่างมีประสิทธิภาพในการดําเนินการพร้อมกันในขณะที่เคารพขีดจํากัดขนาดพูล ใช้ AsyncConnectionPool จาก psycopg_pool asyncio.gather และเพื่อดําเนินการค้นหาหลายรายการพร้อมกัน

ความยืดหยุ่นในการเชื่อมต่อ

ปัญหาเครือข่าย การรีสตาร์ทเซิร์ฟเวอร์ และการเฟลโอเวอร์อาจขัดจังหวะการเชื่อมต่อฐานข้อมูล แอพพลิเคชั่นที่แข็งแกร่งจัดการกับสิ่งเหล่านี้ได้อย่างสง่างาม

ความล้มเหลวชั่วคราว เช่น เครือข่ายบลิป การรีเซ็ตการเชื่อมต่อ และความไม่พร้อมใช้งานของเซิร์ฟเวอร์ชั่วครู่ระหว่างการบํารุงรักษาเป็นสิ่งที่หลีกเลี่ยงไม่ได้ในสภาพแวดล้อมระบบคลาวด์ การใช้ตรรกะการลองใหม่ด้วยการแบ็คออฟแบบเอ็กซ์โพเนนเชียลจะช่วยให้แอปพลิเคชันของคุณกู้คืนจากปัญหาชั่วคราวเหล่านี้ได้อย่างราบรื่นโดยไม่ทําให้เซิร์ฟเวอร์ล้นหลามด้วยการพยายามลองใหม่ในทันที เพิ่มความกระวนกระวายใจแบบสุ่มเพื่อป้องกันไม่ให้อินสแตนซ์แอปพลิเคชันหลายรายการลองใหม่พร้อมกัน จับ OperationalError ข้อยกเว้น คํานวณเวลารอเป็น (2 ** attempt) + random.uniform(0, 1)และลองอีกครั้งตามจํานวนครั้งสูงสุด

การหมดเวลาจะป้องกันไม่ให้แอปพลิเคชันของคุณรออย่างไม่มีกําหนดเมื่อฐานข้อมูลทํางานช้าหรือไม่สามารถเข้าถึงได้ การหมดเวลาการเชื่อมต่อจะจํากัดระยะเวลาในการรอเมื่อสร้างการเชื่อมต่อใหม่ ในขณะที่การหมดเวลาของคําสั่งจะจํากัดเวลาในการดําเนินการคิวรี สําหรับแอปพลิเคชันการค้นหาเวกเตอร์ ให้เลือกการหมดเวลาที่รองรับการสืบค้นที่ถูกต้องช้าที่สุดของคุณในขณะที่ล้มเหลวอย่างรวดเร็วในการค้นหาที่เกินเวลาแฝงที่ยอมรับได้ กําหนดค่าการหมดเวลาในสตริงการเชื่อมต่อของคุณโดยใช้พารามิเตอร์เช่น connect_timeout=10 และoptions=-c statement_timeout=30000 สําหรับคิวรีเวกเตอร์ ให้ตั้งค่าการหมดเวลาของคําสั่งที่รองรับคิวรีที่ยอมรับได้ช้าที่สุด การหมดเวลา 30 วินาทีนั้นสมเหตุสมผลสําหรับการค้นหาเวกเตอร์ที่ซับซ้อน แอปพลิเคชันแบบโต้ตอบอาจใช้ค่าที่ต่ํากว่า

เมื่อมีการใช้งานการเชื่อมต่อพูลทั้งหมดและคําขอใหม่มาถึง พูลจะต้องจัดคิวคําขอ (เพิ่มเวลาแฝง) หรือปฏิเสธทันที ไม่มีตัวเลือกใดที่เหมาะ ดังนั้นการตรวจสอบการใช้พูลจะช่วยให้คุณปรับขนาดได้ก่อนที่จะเกิดความเหนื่อยล้าบ่อยครั้ง เมื่อเกิดความเหนื่อยล้า การส่งกลับข้อความแสดงข้อผิดพลาดที่ชัดเจนจะช่วยให้ไคลเอ็นต์ใช้ตรรกะการลองใหม่ของตนเองแทนที่จะหมดเวลาอย่างคาดเดาไม่ได้ จัดการ PoolTimeout ข้อยกเว้นโดยส่งคืนข้อผิดพลาดที่สง่างาม เช่น {"error": "Service temporarily busy, please retry"}. ตรวจสอบการใช้สระและขนาดหากเกิดความเหนื่อยล้าบ่อยครั้ง

แหล่งข้อมูลเพิ่มเติม