หนึ่งในลำดับความสำคัญหลักของทีมวิศวกร ApeeScape คือการโยกย้ายไปยังสถาปัตยกรรมที่ใช้บริการ องค์ประกอบที่สำคัญของการริเริ่มคือ การสกัดการเรียกเก็บเงิน ซึ่งเป็นโครงการที่เราแยกฟังก์ชันการเรียกเก็บเงินจากแพลตฟอร์ม ApeeScape เพื่อปรับใช้เป็นบริการแยกต่างหาก
ในช่วงไม่กี่เดือนที่ผ่านมาเราได้แยกส่วนแรกของฟังก์ชันการทำงาน ในการรวมการเรียกเก็บเงินกับบริการอื่น ๆ เราใช้ทั้งไฟล์ อะซิงโครนัส API ( คาฟคา -based) และก ซิงโครนัส API (ตาม HTTP)
บทความนี้เป็นบันทึกเกี่ยวกับความพยายามของเราในการเพิ่มประสิทธิภาพและเสถียรภาพของ API แบบซิงโครนัส
นี่เป็นขั้นตอนแรกของการริเริ่มของเรา ในการเดินทางของเราไปสู่การสกัดการเรียกเก็บเงินเต็มรูปแบบเราพยายามทำงานในลักษณะที่เพิ่มขึ้นเพื่อส่งมอบการเปลี่ยนแปลงเล็กน้อยและปลอดภัยในการผลิต (ดู สไลด์จากการพูดคุยที่ยอดเยี่ยม เกี่ยวกับอีกแง่มุมหนึ่งของโปรเจ็กต์นี้: การแยกเอ็นจิ้นที่เพิ่มขึ้นจากแอพ Rails)
จุดเริ่มต้นคือ แพลตฟอร์ม ApeeScape แอปพลิเคชั่น Ruby on Rails เสาหิน เราเริ่มต้นด้วยการระบุรอยต่อระหว่างการเรียกเก็บเงินและแพลตฟอร์ม ApeeScape ที่ระดับข้อมูล แนวทางแรกคือการแทนที่ บันทึกการใช้งาน (AR) สัมพันธ์กับการเรียกใช้วิธีปกติ ต่อไปเราต้องใช้การเรียก REST ไปยังบริการเรียกเก็บเงินที่ดึงข้อมูลที่ส่งคืนโดยวิธีการ
เราปรับใช้บริการเรียกเก็บเงินขนาดเล็กที่เข้าถึงฐานข้อมูลเดียวกับแพลตฟอร์ม เราสามารถสอบถามการเรียกเก็บเงินโดยใช้ HTTP API หรือด้วยการโทรโดยตรงไปยังฐานข้อมูล แนวทางนี้ทำให้เราสามารถใช้ทางเลือกที่ปลอดภัยได้ ในกรณีที่คำขอ HTTP ล้มเหลวไม่ว่าด้วยเหตุผลใด ๆ (การใช้งานที่ไม่ถูกต้องปัญหาด้านประสิทธิภาพปัญหาการปรับใช้) เราใช้การโทรโดยตรงและส่งคืนผลลัพธ์ที่ถูกต้องไปยังผู้โทร
เพื่อให้การเปลี่ยนภาพปลอดภัยและราบรื่นเราใช้แฟล็กคุณลักษณะเพื่อสลับระหว่าง HTTP และการโทรโดยตรง น่าเสียดายที่ความพยายามครั้งแรกที่ใช้กับ REST ได้รับการพิสูจน์แล้วว่าช้าอย่างไม่อาจยอมรับได้ เพียงแค่แทนที่ความสัมพันธ์ของ AR ด้วยคำขอระยะไกลทำให้เกิดปัญหาเมื่อเปิดใช้งาน HTTP แม้ว่าเราจะเปิดใช้งานสำหรับการโทรเพียงเล็กน้อย แต่ปัญหาก็ยังคงอยู่
เรารู้ว่าเราต้องการแนวทางที่แตกต่างอย่างสิ้นเชิง
เราตัดสินใจที่จะ แทนที่ REST ด้วย GraphQL (GQL) เพื่อให้เกิดความยืดหยุ่นมากขึ้นในฝั่งไคลเอ็นต์ เราต้องการทำการตัดสินใจโดยอาศัยข้อมูลในช่วงการเปลี่ยนแปลงนี้เพื่อให้สามารถทำนายผลลัพธ์ในครั้งนี้ได้
ในการดำเนินการดังกล่าวเราได้ควบคุมทุกคำขอจากแพลตฟอร์ม ApeeScape (เสาหิน) ไปจนถึงการเรียกเก็บเงินและข้อมูลรายละเอียดที่บันทึกไว้: เวลาตอบสนองพารามิเตอร์ข้อผิดพลาดและแม้แต่การติดตามสแต็ก (เพื่อทำความเข้าใจว่าส่วนใดของแพลตฟอร์มใช้การเรียกเก็บเงิน) สิ่งนี้ทำให้เราตรวจพบฮอตสปอต - ตำแหน่งในโค้ดที่ส่งคำขอจำนวนมากหรือสิ่งที่ทำให้เกิดการตอบสนองช้า จากนั้นด้วย stacktrace และ พารามิเตอร์ เราสามารถสร้างปัญหาซ้ำในพื้นที่และมีลูปข้อเสนอแนะสั้น ๆ สำหรับการแก้ไขมากมาย
เพื่อหลีกเลี่ยงความประหลาดใจที่น่ารังเกียจในการผลิตเราได้เพิ่มธงคุณลักษณะอีกระดับ เรามีแฟล็กเดียวต่อวิธีใน API เพื่อย้ายจาก REST ไปยัง GraphQL เรากำลังเปิดใช้งาน HTTP อย่างค่อยเป็นค่อยไปและดูว่ามี 'สิ่งไม่ดี' ปรากฏในบันทึกหรือไม่
ในกรณีส่วนใหญ่“ สิ่งที่ไม่ดี” อาจเป็นเวลาตอบสนองที่ยาวนาน (หลายวินาที) 429 Too Many Requests
หรือ 502 Bad Gateway
เราใช้รูปแบบต่างๆเพื่อแก้ไขปัญหาเหล่านี้: การโหลดล่วงหน้าและการแคชข้อมูลการ จำกัด ข้อมูลที่ดึงมาจากเซิร์ฟเวอร์การเพิ่มความกระวนกระวายใจและการ จำกัด อัตรา
ปัญหาแรกที่เราสังเกตเห็นคือคำขอจำนวนมากที่ส่งจากคลาส / มุมมองเดียวซึ่งคล้ายกับไฟล์ ปัญหา N + 1 ใน SQL
การโหลดล่วงหน้าของ Active Record ไม่สามารถใช้งานได้เกินขอบเขตของบริการและด้วยเหตุนี้เราจึงมีหน้าเดียวที่ส่งคำขอเรียกเก็บเงินถึง 1,000 คำขอทุกครั้งที่โหลดซ้ำ คำขอนับพันจากหน้าเดียว! สถานการณ์ในงานเบื้องหลังบางอย่างไม่ได้ดีขึ้นมากนัก เราต้องการส่งคำขอหลายสิบรายการมากกว่าหลายพันคำขอ
งานเบื้องหลังอย่างหนึ่งกำลังดึงข้อมูลงาน (ขอเรียกโมเดลนี้ว่า Product
) และตรวจสอบว่าผลิตภัณฑ์ควรถูกทำเครื่องหมายว่าไม่ใช้งานตามข้อมูลการเรียกเก็บเงิน (สำหรับตัวอย่างนี้เราจะเรียกโมเดล BillingRecord
). แม้ว่าจะมีการดึงข้อมูลผลิตภัณฑ์เป็นกลุ่ม แต่จะมีการร้องขอข้อมูลการเรียกเก็บเงินทุกครั้งที่จำเป็น ทุกผลิตภัณฑ์จำเป็นต้องมีบันทึกการเรียกเก็บเงินดังนั้นการประมวลผลทุกผลิตภัณฑ์จึงทำให้มีการร้องขอไปยังบริการเรียกเก็บเงินเพื่อเรียกข้อมูลเหล่านั้น นั่นหมายถึงหนึ่งคำขอต่อผลิตภัณฑ์และส่งผลให้มีคำขอประมาณ 1,000 รายการที่ส่งมาจากการดำเนินการงานเดียว
เพื่อแก้ไขปัญหานี้เราได้เพิ่มการโหลดบันทึกการเรียกเก็บเงินล่วงหน้าเป็นกลุ่ม สำหรับผลิตภัณฑ์ทุกชุดที่ดึงมาจากฐานข้อมูลเราขอบันทึกการเรียกเก็บเงินหนึ่งครั้งจากนั้นกำหนดให้ผลิตภัณฑ์นั้น ๆ :
นักพัฒนาแฟลชและแอนิเมชั่นแฟลช
# fetch all required billing records and assign them to respective products def cache_billing_records(products) # array of billing records billing_records = Billing::QueryService .billing_records_for_products(*products) indexed_records = billing_records.group_by(&:product_gid) products.each do |p| e.cache_billing_records!(indexed_records[p.gid].to_a) } end end
ด้วยชุดงาน 100 รายการและคำขอเดียวไปยังบริการเรียกเก็บเงินต่อชุดงานเราเปลี่ยนจาก ~ 1,000 คำขอต่องานเป็น ~ 10
คำขอจัดกลุ่มและบันทึกการเรียกเก็บเงินจะทำงานได้ดีเมื่อเรามีชุดผลิตภัณฑ์และเราต้องการบันทึกการเรียกเก็บเงินของพวกเขา แต่ในทางกลับกันถ้าเราดึงบันทึกการเรียกเก็บเงินแล้วพยายามใช้ผลิตภัณฑ์ของตนโดยดึงมาจากฐานข้อมูลแพลตฟอร์ม
ตามที่คาดไว้สิ่งนี้ทำให้เกิดปัญหา N + 1 อีกครั้งคราวนี้ที่ฝั่งแพลตฟอร์ม เมื่อเราใช้ผลิตภัณฑ์เพื่อรวบรวมบันทึกการเรียกเก็บเงิน N เรากำลังดำเนินการสืบค้นฐานข้อมูล N
วิธีแก้ปัญหาคือดึงผลิตภัณฑ์ที่จำเป็นทั้งหมดในครั้งเดียวจัดเก็บเป็นแฮชที่จัดทำดัชนีโดย ID จากนั้นกำหนดให้กับบันทึกการเรียกเก็บเงินตามลำดับ การใช้งานที่ง่ายขึ้นคือ:
def product_billing_records(products) products_by_gid = products.index_by(&:gid) product_gids = products_by_gid.keys.compact return [] if product_gids.blank? billing_records = fetch_billing_records(product_gids: product_gids) billing_records.each do |billing_record| billing_record.preload_product!( products_by_gid[billing_record.product_gid] ) end end
หากคุณคิดว่ามันคล้ายกับไฟล์ แฮชเข้าร่วม , คุณไม่ได้โดดเดี่ยว.
เราต่อสู้กับคำขอและปัญหา N + 1 ที่เพิ่มขึ้นอย่างรวดเร็วที่สุดในฝั่งแพลตฟอร์ม เรายังคงตอบสนองช้าอยู่ เราพบว่าเกิดจากการโหลดข้อมูลไปยังแพลตฟอร์มมากเกินไปและกรองข้อมูลที่นั่น (การกรองฝั่งไคลเอ็นต์) การโหลดข้อมูลไปยังหน่วยความจำการทำให้เป็นอนุกรมการส่งผ่านเครือข่ายและการแยกข้อมูลออกจากระบบเพียงเพื่อปล่อยทิ้งส่วนใหญ่เป็นขยะจำนวนมหาศาล สะดวกในระหว่างการใช้งานเนื่องจากเรามีจุดสิ้นสุดทั่วไปและใช้ซ้ำได้ ในระหว่างการดำเนินการพิสูจน์แล้วว่าใช้ไม่ได้ เราต้องการสิ่งที่เฉพาะเจาะจงมากขึ้น
เราแก้ไขปัญหาโดย เพิ่มอาร์กิวเมนต์การกรองให้กับ GraphQL . แนวทางของเราคล้ายกับการเพิ่มประสิทธิภาพที่รู้จักกันดีซึ่งประกอบด้วยการย้ายการกรองจากระดับแอปไปยังแบบสอบถาม DB (find_all
เทียบกับ where
ใน Rails) ในโลกของฐานข้อมูลแนวทางนี้ชัดเจนและมีให้ในชื่อ WHERE
ใน SELECT
แบบสอบถาม ในกรณีนี้เราต้องดำเนินการจัดการแบบสอบถามด้วยตัวเอง (ในการเรียกเก็บเงิน)
เราปรับใช้ตัวกรองและรอดูการปรับปรุงประสิทธิภาพ แต่เราพบข้อผิดพลาด 502 บนแพลตฟอร์ม (และผู้ใช้ของเราก็เห็นข้อผิดพลาดเช่นกัน) ไม่ดี. ไม่ดีเลย!
เหตุใดจึงเกิดขึ้น การเปลี่ยนแปลงนั้นควรมีเวลาตอบสนองที่ดีขึ้นไม่ใช่ทำลายบริการ เราได้แนะนำข้อบกพร่องที่ละเอียดอ่อนโดยไม่ได้ตั้งใจ เราเก็บ API ทั้งสองเวอร์ชัน (GQL และ REST) ไว้ที่ฝั่งไคลเอ็นต์ เราค่อยๆเปลี่ยนด้วยแฟล็กฟีเจอร์ รุ่นแรกที่โชคร้ายที่เรานำมาใช้ได้แนะนำการถดถอยในสาขา REST เดิม เราเน้นการทดสอบในสาขา GQL ดังนั้นเราจึงพลาดปัญหาด้านประสิทธิภาพใน REST บทเรียนที่เรียนรู้: หากไม่มีพารามิเตอร์การค้นหาให้ส่งคืนคอลเล็กชันว่างไม่ใช่ทุกสิ่งที่คุณมีในฐานข้อมูลของคุณ
ลองดูที่ NewRelic
ข้อมูลสำหรับการเรียกเก็บเงิน เราปรับใช้การเปลี่ยนแปลงด้วยการกรองฝั่งเซิร์ฟเวอร์ในช่วงที่มีการจราจรติดขัด (เราปิดการรับส่งข้อมูลการเรียกเก็บเงินหลังจากพบปัญหาแพลตฟอร์ม) คุณจะเห็นได้ว่าการตอบกลับเร็วขึ้นและคาดเดาได้มากขึ้นหลังการปรับใช้
การเพิ่มตัวกรองลงในสคีมา GQL ไม่ใช่เรื่องยากเกินไป สถานการณ์ที่ GraphQL การส่องผ่านเป็นกรณีที่เราดึงข้อมูลในช่องมากเกินไปไม่ใช่วัตถุมากเกินไป ด้วย REST เรากำลังส่งข้อมูลทั้งหมดที่อาจจำเป็น การสร้างจุดสิ้นสุดทั่วไปบังคับให้เราบรรจุข้อมูลและการเชื่อมโยงทั้งหมดที่ใช้บนแพลตฟอร์ม
ด้วย GQL เราสามารถเลือกช่องต่างๆ แทนที่จะดึงข้อมูลมากกว่า 20 ฟิลด์ที่ต้องโหลดตารางฐานข้อมูลหลาย ๆ ช่องเราเลือกเพียงสามถึงห้าฟิลด์ที่จำเป็น สิ่งนี้ทำให้เราสามารถลบการใช้งานการเรียกเก็บเงินที่เพิ่มขึ้นอย่างกะทันหันในระหว่างการปรับใช้แพลตฟอร์มเนื่องจากคำค้นหาบางส่วนถูกใช้โดยงานที่สร้างดัชนีใหม่ในการค้นหาแบบยืดหยุ่นที่เรียกใช้ระหว่างการปรับใช้ ผลข้างเคียงที่เป็นบวกทำให้การปรับใช้งานรวดเร็วและเชื่อถือได้มากขึ้น
เรา จำกัด จำนวนออบเจ็กต์ที่ดึงข้อมูลและจำนวนข้อมูลที่บรรจุในทุกออบเจ็กต์ เราจะทำอะไรได้อีก? อาจจะ ไม่ดึงข้อมูลเลย เหรอ?
เราสังเกตเห็นพื้นที่อื่นที่มีช่องว่างสำหรับการปรับปรุง: เราใช้วันที่สร้างบันทึกการเรียกเก็บเงินครั้งล่าสุดในแพลตฟอร์มบ่อยครั้งและทุกครั้งเรากำลังเรียกเก็บเงินเพื่อเรียกข้อมูลดังกล่าว เราตัดสินใจว่าแทนที่จะเรียกข้อมูลพร้อมกันทุกครั้งที่จำเป็นเราสามารถแคชตามเหตุการณ์ที่ส่งมาจากการเรียกเก็บเงิน
เราวางแผนล่วงหน้าเตรียมงาน (สี่ถึงห้างาน) และเริ่มดำเนินการเพื่อให้เสร็จโดยเร็วที่สุดเนื่องจากคำขอเหล่านั้นก่อให้เกิดภาระที่สำคัญ เรามีงานล่วงหน้าสองสัปดาห์
โชคดีที่ไม่นานหลังจากที่เราเริ่มต้นเราได้ตรวจสอบปัญหาครั้งที่สองและตระหนักว่าเราสามารถใช้ข้อมูลที่มีอยู่แล้วบนแพลตฟอร์มได้ แต่อยู่ในรูปแบบอื่น แทนที่จะเพิ่มตารางใหม่เพื่อแคชข้อมูลจาก Kafka เราใช้เวลาสองสามวันในการเปรียบเทียบข้อมูลจากการเรียกเก็บเงินและแพลตฟอร์ม นอกจากนี้เรายังได้ปรึกษาผู้เชี่ยวชาญด้านโดเมนว่าเราสามารถใช้ข้อมูลแพลตฟอร์มได้หรือไม่
สุดท้ายเราแทนที่การโทรระยะไกลด้วยแบบสอบถาม DB นั่นเป็นชัยชนะครั้งใหญ่จากมุมมองด้านประสิทธิภาพและปริมาณงาน เรายังประหยัดเวลาในการพัฒนาได้มากกว่าหนึ่งสัปดาห์
เรากำลังติดตั้งและปรับใช้การเพิ่มประสิทธิภาพเหล่านั้นทีละรายการ แต่ยังคงมีบางกรณีที่การเรียกเก็บเงินตอบกลับด้วย 429 Too Many Requests
เราสามารถมี เพิ่มขีด จำกัด คำขอบน Nginx แต่เราต้องการทำความเข้าใจปัญหาให้ดีขึ้นเนื่องจากเป็นการบอกใบ้ว่าการสื่อสารไม่ทำงานตามที่คาดไว้ ดังที่คุณอาจจำได้เราอาจมีข้อผิดพลาดเหล่านั้นในการผลิตเนื่องจากผู้ใช้ปลายทางมองไม่เห็น (เนื่องจากทางเลือกกลับเป็นการโทรโดยตรง)
ข้อผิดพลาดเกิดขึ้นทุกวันอาทิตย์เมื่อแพลตฟอร์มกำหนดเวลาการแจ้งเตือนสำหรับสมาชิกเครือข่ายพรสวรรค์เกี่ยวกับแผ่นเวลาที่เกินกำหนด ในการส่งการแจ้งเตือนงานจะดึงข้อมูลการเรียกเก็บเงินสำหรับผลิตภัณฑ์ที่เกี่ยวข้องซึ่งรวมถึงบันทึกหลายพันรายการ สิ่งแรกที่เราทำเพื่อเพิ่มประสิทธิภาพคือการจัดกลุ่มและการโหลดข้อมูลการเรียกเก็บเงินล่วงหน้าและดึงเฉพาะฟิลด์ที่จำเป็นเท่านั้น ทั้งสองอย่างเป็นเทคนิคที่รู้จักกันดีดังนั้นเราจะไม่ลงรายละเอียดในที่นี้
เราปรับใช้และรอในวันอาทิตย์ถัดไป เรามั่นใจว่าได้แก้ไขปัญหาแล้ว อย่างไรก็ตามในวันอาทิตย์เกิดข้อผิดพลาดอีกครั้ง
บริการเรียกเก็บเงินไม่เพียง แต่เรียกใช้ในระหว่างการตั้งเวลาเท่านั้น แต่ยังรวมถึงเมื่อมีการส่งการแจ้งเตือนไปยังสมาชิกเครือข่ายด้วย การแจ้งเตือนจะถูกส่งไปในงานเบื้องหลังแยกกัน (โดยใช้ Sidekiq ) ดังนั้นการโหลดล่วงหน้าจึงไม่เป็นปัญหา ในตอนแรกเราสันนิษฐานว่ามันจะไม่มีปัญหาเพราะไม่ใช่ทุกผลิตภัณฑ์ที่ต้องการการแจ้งเตือนและเนื่องจากการแจ้งเตือนทั้งหมดจะถูกส่งไปพร้อมกัน การแจ้งเตือนกำหนดไว้สำหรับ 17.00 น. ตามเขตเวลาของสมาชิกเครือข่าย แม้ว่าเราจะพลาดรายละเอียดที่สำคัญ: สมาชิกของเราไม่ได้กระจายไปตามโซนเวลาอย่างสม่ำเสมอ
เรากำลังตั้งเวลาการแจ้งเตือนไปยังสมาชิกเครือข่ายหลายพันคนซึ่งประมาณ 25% อยู่ในเขตเวลาเดียว ประมาณ 15% อาศัยอยู่ในเขตเวลาที่มีประชากรมากเป็นอันดับสอง เมื่อนาฬิกาเดินไปที่ 17:00 น. ในเขตเวลาเหล่านั้นเราจึงต้องส่งการแจ้งเตือนหลายร้อยรายการพร้อมกัน นั่นหมายถึงคำขอจำนวนหลายร้อยรายการไปยังบริการเรียกเก็บเงินซึ่งมากกว่าที่บริการจะจัดการได้
ไม่สามารถโหลดข้อมูลการเรียกเก็บเงินล่วงหน้าได้เนื่องจากการช่วยเตือนกำหนดเวลาไว้ในงานอิสระ เราไม่สามารถดึงข้อมูลจากการเรียกเก็บเงินในช่องได้น้อยลงเนื่องจากเราได้เพิ่มประสิทธิภาพจำนวนดังกล่าวแล้ว การย้ายสมาชิกเครือข่ายไปยังเขตเวลาที่มีประชากรน้อยก็ไม่เป็นผลเช่นกัน แล้วเราทำอะไร? เราย้ายการช่วยเตือนเพียงเล็กน้อย
เราเพิ่ม กระวนกระวายใจ จนถึงเวลาที่กำหนดเวลาการแจ้งเตือนเพื่อหลีกเลี่ยงสถานการณ์ที่จะส่งการแจ้งเตือนทั้งหมดในเวลาเดียวกัน แทนที่จะจัดตารางเวลา 17.00 น. แบบคมเรากำหนดเวลาไว้ภายในช่วงสองนาทีระหว่าง 17.59 น. ถึง 18:01 น.
เราปรับใช้บริการและรอวันอาทิตย์ถัดไปมั่นใจว่าเราจะแก้ไขปัญหาได้ในที่สุด น่าเสียดายที่ในวันอาทิตย์ข้อผิดพลาดปรากฏขึ้นอีกครั้ง
เรางงงวย จากการคำนวณของเราคำขอควรจะกระจายไปในช่วงเวลา 2 นาทีซึ่งหมายความว่าเราจะมีคำขอมากที่สุดสองคำขอต่อวินาที นั่นไม่ใช่สิ่งที่บริการไม่สามารถจัดการได้ เราวิเคราะห์บันทึกและการกำหนดเวลาของคำขอเรียกเก็บเงินและเราพบว่าการใช้งานที่ไม่ได้ผลคำขอของเราจึงยังคงปรากฏอยู่ในกลุ่มที่ จำกัด
พฤติกรรมนั้นเกิดจากอะไร เป็นวิธีที่ Sidekiq ดำเนินการจัดตารางเวลา มัน การสำรวจความคิดเห็น redis ทุกๆ 10–15 วินาที และด้วยเหตุนี้จึงไม่สามารถแสดงความละเอียด 1 วินาทีได้ เพื่อให้เกิดการกระจายคำขออย่างสม่ำเสมอเราจึงใช้ Sidekiq::Limiter
- คลาสที่จัดทำโดย Sidekiq Enterprise . เราใช้ตัว จำกัด หน้าต่างที่อนุญาตแปดคำขอสำหรับการย้ายหน้าต่างหนึ่งวินาที เราเลือกค่านั้นเนื่องจากเรา จำกัด Nginx ไว้ที่ 10 คำขอต่อวินาทีในการเรียกเก็บเงิน เราเก็บรหัสความกระวนกระวายใจไว้เพราะมันให้การกระจายคำขอแบบหยาบ: มันกระจายงาน Sidekiq ในช่วงเวลาสองนาที จากนั้นใช้ Sidekiq Limiter เพื่อให้แน่ใจว่างานแต่ละกลุ่มได้รับการประมวลผลโดยไม่ทำลายขีด จำกัด ที่กำหนดไว้
อีกครั้งที่เราปรับใช้และรอวันอาทิตย์ เรามั่นใจว่าในที่สุดเราก็แก้ไขปัญหาได้แล้วและเราก็ทำสำเร็จ ข้อผิดพลาดหายไป
ฉันเชื่อว่าคุณไม่แปลกใจกับโซลูชันที่เรานำมาใช้ การจัดกลุ่มการกรองฝั่งเซิร์ฟเวอร์การส่งเฉพาะช่องที่จำเป็นและการ จำกัด อัตราไม่ใช่เทคนิคใหม่ วิศวกรซอฟต์แวร์ที่มีประสบการณ์ใช้งานในบริบทที่แตกต่างกันอย่างไม่ต้องสงสัย
การโหลดล่วงหน้าเพื่อหลีกเลี่ยง N + 1? เรามีในทุกออม แฮชเข้าร่วม เหรอ? แม้แต่ MySQL ก็มีแล้ว Underfetching? SELECT *
เทียบกับ SELECT field
เป็นเคล็ดลับที่รู้จักกันดี กระจายภาระ? ไม่ใช่แนวคิดใหม่เช่นกัน
แล้วทำไมฉันถึงเขียนบทความนี้? ทำไมเราไม่ทำ ตั้งแต่เริ่มต้น เหรอ? ตามปกติบริบทเป็นกุญแจสำคัญ เทคนิคเหล่านี้หลายอย่างดูคุ้นเคยหลังจากที่เรานำไปใช้หรือเฉพาะเมื่อเราสังเกตเห็นปัญหาในการผลิตที่จำเป็นต้องได้รับการแก้ไขไม่ใช่เมื่อเราจ้องที่โค้ด
มีคำอธิบายที่เป็นไปได้หลายประการสำหรับสิ่งนั้น ส่วนใหญ่เราพยายามทำ สิ่งที่ง่ายที่สุดที่สามารถใช้ได้ เพื่อหลีกเลี่ยงการใช้วิศวกรรมมากเกินไป เราเริ่มต้นด้วยโซลูชัน REST ที่น่าเบื่อจากนั้นจึงย้ายไปที่ GQL เราปรับใช้การเปลี่ยนแปลงที่อยู่เบื้องหลังการตั้งค่าสถานะคุณลักษณะตรวจสอบการทำงานของทุกอย่างด้วยการเข้าชมเพียงเล็กน้อยและนำการปรับปรุงไปใช้ตามข้อมูลจริง
การค้นพบอย่างหนึ่งของเราคือการลดประสิทธิภาพการทำงานเป็นเรื่องง่ายที่จะมองข้ามเมื่อทำการ refactoring (และการสกัดสามารถถือว่าเป็นการปรับโครงสร้างใหม่อย่างมีนัยสำคัญ) การเพิ่มขอบเขตที่เข้มงวดหมายความว่าเราตัดความสัมพันธ์ที่เพิ่มเข้ามาเพื่อเพิ่มประสิทธิภาพโค้ด แม้ว่าจะยังไม่ปรากฏชัดจนกว่าเราจะวัดประสิทธิภาพ สุดท้ายในบางกรณีเราไม่สามารถสร้างทราฟฟิกการผลิตซ้ำในสภาพแวดล้อมการพัฒนาได้
เรามุ่งมั่นที่จะมีพื้นผิว HTTP API สากลขนาดเล็กของบริการเรียกเก็บเงิน ด้วยเหตุนี้เราจึงได้รับปลายทาง / แบบสอบถามสากลจำนวนมากที่มีข้อมูลที่จำเป็นในกรณีการใช้งานที่แตกต่างกัน และนั่นหมายความว่าในหลาย ๆ กรณีการใช้งานข้อมูลส่วนใหญ่ไม่มีประโยชน์ มันเป็นบิตของ การแลกเปลี่ยนระหว่าง DRY และ นั่นคือ : ด้วย DRY เรามีปลายทาง / ข้อความค้นหาเพียงรายการเดียวที่ส่งคืนบันทึกการเรียกเก็บเงินในขณะที่ใช้ YAGNI เราพบข้อมูลที่ไม่ได้ใช้ในปลายทางซึ่งเป็นอันตรายต่อประสิทธิภาพเท่านั้น
นอกจากนี้เรายังสังเกตเห็นการแลกเปลี่ยนอีกครั้งเมื่อพูดคุยเกี่ยวกับความกระวนกระวายใจกับทีมเรียกเก็บเงิน จากมุมมองของไคลเอ็นต์ (แพลตฟอร์ม) ทุกคำขอควรได้รับการตอบสนองเมื่อแพลตฟอร์มต้องการ ปัญหาด้านประสิทธิภาพและการโอเวอร์โหลดของเซิร์ฟเวอร์ควรซ่อนอยู่หลังส่วนที่เป็นนามธรรมของบริการเรียกเก็บเงิน จากมุมมองของบริการการเรียกเก็บเงินเราต้องหาวิธีที่จะทำให้ลูกค้าทราบถึงลักษณะการทำงานของเซิร์ฟเวอร์ที่จะทนต่อภาระ
อีกครั้งที่นี่ไม่มีอะไรแปลกใหม่หรือแหวกแนว มันเกี่ยวกับการระบุรูปแบบที่เป็นที่รู้จักในบริบทต่างๆและทำความเข้าใจการแลกเปลี่ยนที่เกิดจากการเปลี่ยนแปลง เราได้เรียนรู้ว่าเป็นวิธีที่ยากลำบากและเราหวังว่าเราจะช่วยคุณไม่ให้ทำผิดซ้ำอีก แทนที่จะทำผิดซ้ำ ๆ คุณจะไม่ต้องสงสัยเลยว่าทำผิดพลาดเองและเรียนรู้จากสิ่งเหล่านั้น
ขอขอบคุณเป็นพิเศษสำหรับเพื่อนร่วมงานและเพื่อนร่วมทีมที่มีส่วนร่วมในความพยายามของเรา:
API ภายนอกคือ API สำหรับไคลเอนต์หรือ UI API ภายในใช้สำหรับการสื่อสารระหว่างบริการ
GraphQL อนุญาตให้ใช้เลเยอร์ API ที่ยืดหยุ่นได้ สนับสนุนการกำหนดขอบเขตและการจัดกลุ่มข้อมูลเพื่อให้ไคลเอ็นต์ดึงเฉพาะข้อมูลที่จำเป็นและสามารถเรียกดูได้ในคำขอเดียวไปยังเซิร์ฟเวอร์
มีรูปแบบการใช้งานที่แตกต่างกันและไม่สามารถเปรียบเทียบได้โดยตรง ในกรณีของเรา GraphQL พิสูจน์แล้วว่าเหมาะสมกว่า แต่ระยะทางของคุณอาจแตกต่างกันไป
ขึ้นอยู่กับ API การโหลดล่วงหน้าและการแคชข้อมูลการ จำกัด ข้อมูลที่ดึงมาจากเซิร์ฟเวอร์การเพิ่มความกระวนกระวายใจและการ จำกัด อัตราสามารถใช้เพื่อเพิ่มประสิทธิภาพ API ภายในได้