3 คะแนน โดย GN⁺ 2024-04-17 | 2 ความคิดเห็น | แชร์ทาง WhatsApp

การสร้างคลังข้อมูลสภาพอากาศ ตอนที่ 1: โหลดข้อมูลสภาพอากาศ 1 ล้านล้านแถวเข้า TimescaleDB

ความหมายของสิ่งที่เรากำลังทำ

เหตุผลที่สร้างคลังข้อมูลสภาพอากาศ

  • มีแนวคิดว่าการรวบรวมและวิเคราะห์ข้อมูลสภาพอากาศย้อนหลังจากทั่วโลกเพื่อตรวจหาสัญญาณของการเปลี่ยนแปลงสภาพภูมิอากาศน่าจะเป็นประโยชน์
  • หากมีคลังข้อมูลสภาพอากาศขนาดใหญ่ ก็จะสามารถดูได้เป็นรายภูมิภาคว่า จาการ์ตาร้อนขึ้นจริงหรือไม่ หรือพายุรุนแรงขึ้นหรือไม่ รวมถึงชิลีกำลังร้อนขึ้นโดยรวม หรือมีเมฆมากขึ้นหรือไม่
  • จากสิ่งนี้ เราสามารถระบุได้ว่าพื้นที่ใดบนโลกเผชิญกับการเปลี่ยนแปลงสภาพภูมิอากาศมากที่สุด และเกิดการเปลี่ยนแปลงในรูปแบบใดบ้าง
  • เพื่อทำการวิเคราะห์ลักษณะนี้ในระดับโลก จำเป็นต้องเพิ่มความเร็วในการสืบค้นของคลังข้อมูล และปริมาณข้อมูลก็มีขนาดมหาศาลมาก
  • ขั้นตอนแรกคือการโหลดข้อมูลเข้า PostgreSQL โดยดูมีแนวโน้มดีที่จะใช้ TimescaleDB เพื่อเพิ่มความเร็วของคิวรีแบบอนุกรมเวลา และใช้ PostGIS เพื่อเพิ่มความเร็วของคิวรีเชิงภูมิสารสนเทศ

แนะนำข้อมูล

  • ใช้ข้อมูลผลิตภัณฑ์ climate reanalysis ของ ERA5 ไม่ใช่ข้อมูลการสังเกตจริงโดยตรง
  • ERA5 คือผลลัพธ์จากการรันแบบจำลองภูมิอากาศที่ถูกจำกัดด้วยข้อมูลการสังเกต ดังนั้นบริเวณที่มีข้อมูลสังเกตมากจะคล้ายกับข้อมูลจริง ส่วนบริเวณที่ไม่มีข้อมูลสังเกตจะยังคงมีความสอดคล้องทางฟิสิกส์และสอดคล้องกับสถิติภูมิอากาศ
  • ERA5 ให้ข้อมูลรายชั่วโมงครอบคลุมทั้งโลกตั้งแต่ปี 1940 ที่ความละเอียด 0.25 องศา โดยมีข้อมูลมากกว่า 750 ล้านแถวต่อหนึ่งตัวแปร เช่น อุณหภูมิ ปริมาณฝน ปริมาณเมฆ และความเร็วลม
  • การแทรกข้อมูลลักษณะนี้เข้า relational DB อย่างรวดเร็วไม่ใช่เรื่องง่าย

วิธีแทรกข้อมูล

คำสั่ง insert แบบแถวเดียว

  • เป็นวิธีที่ง่ายที่สุด แต่ช้ามาก ด้วยอัตราแทรก 3,000 รายการต่อวินาที จะใช้เวลาประมาณ 8 ปีในการโหลดข้อมูลทั้งหมด
  • มี overhead สูงจากการแยกวิเคราะห์ไวยากรณ์ การตรวจสอบตาราง/คอลัมน์ แผนการรัน การล็อกตาราง การเขียนบัฟเฟอร์ การเขียนดิสก์ และการ commit

insert แบบหลายค่า

  • แทรกหลายแถวด้วยคำสั่ง insert เดียว ลด overhead ของเครือข่าย การแยกวิเคราะห์ไวยากรณ์ และแผนการรัน
  • psycopg3 เร็วที่สุดที่ 25,000~30,000 รายการต่อวินาที
  • แต่ถึงอย่างนั้นก็ยังต้องใช้เวลาประมาณ 10 เดือนในการโหลดข้อมูลทั้งหมด

คำสั่ง copy

  • เป็นวิธีที่เหมาะสำหรับการโหลดข้อมูลจำนวนมาก โดยอ่านจากไฟล์ CSV หรือไฟล์ไบนารีโดยตรง ทำให้ปรับปรุงการแยกวิเคราะห์ การวางแผน และการใช้ WAL ได้ดีขึ้น
  • หากมี CSV อยู่แล้ว ก็สามารถใช้คำสั่ง copy ได้อย่างง่ายดาย
  • copy ของ psycopg3 สามารถแทรกได้มากกว่า 100,000 รายการต่อวินาที และแม้รวม overhead แล้วก็ยังโหลดข้อมูลทั้งหมดได้ภายใน 3 เดือน
  • เมื่อต้องแทรกด้วยความเร็วสูงเป็นเวลานานผ่าน copy จำเป็นต้องระวังคอขวดที่อาจเกิดขึ้น

copy แบบขนาน

  • ทำงาน copy หลายชุดพร้อมกันเพื่อเพิ่มความเร็ว
  • การแทรกลงตารางเดียวไม่ได้รับประโยชน์จากการประมวลผลแบบขนานมากนัก ดังนั้นเมื่อเกิน worker 16 ตัวก็แทบไม่มีประสิทธิภาพเพิ่มขึ้น

การใช้เครื่องมือภายนอก

  • มีการ benchmark ระหว่าง pg_bulkload และ timescaledb-parellel-copy
  • pg_bulkload เร็วกว่า แต่โดยพื้นฐานแล้วข้าม WAL จึงไม่ปลอดภัย
  • timescaledb-parallel-copy สามารถแทรกอย่างปลอดภัยได้มากกว่า 300,000 รายการต่อวินาทีด้วย worker หลายตัว

การปรับตั้งค่า PostgreSQL

  • การปิด fsync และ full_page_writes จะช่วยหลีกเลี่ยงการเขียนดิสก์และทำให้เร็วขึ้นได้ แต่มีความเสี่ยง
  • ตารางแบบ unlogged ก็ไม่ใช้ WAL จึงเร็วกว่า แต่จะถูกตัดทอนเมื่อระบบล้มเหลว และ hypertable ไม่สามารถเป็น unlogged ได้

วิธีที่ดีที่สุดคืออะไร?

  • การใช้ psycopg3 ทำ copy ตรงเข้า hypertable เป็นทางเลือกที่ดีที่สุด หากเป็นไฟล์ CSV ให้ใช้ timescaledb-parallel-copy
  • การประมวลผลแบบขนานที่เหมาะสมคือ worker ราว 12~16 ตัว
  • หากปิดข้อจำกัดด้านความปลอดภัยบางอย่าง อาจไปได้ถึง 460,000 รายการต่อวินาที แต่มีความเสี่ยง
  • หากอัปเกรดฮาร์ดแวร์ก็อาจทำความเร็วได้มากกว่านี้
  • ClickHouse อาจเร็วกว่า แต่เลือก TimescaleDB เพราะต้องการเรียนรู้ PostgreSQL
  • ที่ความเร็ว 460,000 รายการต่อวินาที สามารถโหลดข้อมูลทั้งหมดได้ภายใน 20 วัน

ความเห็นของ GN⁺

  • ความพยายามในการนำข้อมูล ERA5 มาใส่ใน relational DB เพื่อวิเคราะห์นั้นน่าสนใจมาก ปกติเดิมทีการวิเคราะห์ข้อมูล NetCDF โดยตรงด้วย xarray หรือ dask เป็นแนวทางที่พบได้ทั่วไป แต่ถ้าสร้างคลังข้อมูลขึ้นมา ก็จะสามารถทำคิวรีที่ซับซ้อนยิ่งขึ้นได้
  • น่าประทับใจที่ฮาร์ดแวร์ของผู้เขียนเป็นสเปกเมื่อ 5 ปีก่อน แต่ยังสามารถใส่ข้อมูลได้ถึง 460,000 รายการต่อวินาที หากเป็นฮาร์ดแวร์รุ่นใหม่ ก็น่าจะไปถึง 1 ล้านรายการต่อวินาทีได้ อย่างไรก็ตาม การปิด fsync และ full_page_writes อาจกระทบต่อความสมบูรณ์ของฐานข้อมูล จึงต้องระวัง
  • ดูเหมือนความสามารถด้าน parallel processing ของ PostgreSQL จะไม่ได้ช่วยมากนักกับตารางเดี่ยว หากนำ parallel processing มารวมกับ partitioning ก็น่าจะได้ประสิทธิภาพสูงขึ้น และโซลูชันขยายแนวนอนของ Postgres อย่าง Citus ก็เป็นสิ่งที่ควรพิจารณา
  • ประเด็นที่ว่า ERA5 สามารถนำมาใช้วิเคราะห์การเปลี่ยนแปลงสภาพภูมิอากาศได้นั้นน่าสนใจ เพราะช่วยให้วิเคราะห์ภูมิอากาศในอดีตของพื้นที่ที่ขาดข้อมูลการสังเกตได้ แต่ ERA5 ก็มีข้อจำกัดตรงที่เป็นผลลัพธ์จากแบบจำลอง จึงควรคำนึงถึงความไม่แน่นอน แม้จะมีการปรับแก้ด้วยข้อมูลสังเกตแล้วก็ตาม
  • สำหรับแพลตฟอร์มวิเคราะห์ โดยทั่วไปมักใช้คลังข้อมูลบนคลาวด์อย่าง Snowflake หรือ BigQuery แต่การลงมือจัดการฮาร์ดแวร์เองและเรียนรู้แบบผู้เขียนก็มีคุณค่าไม่น้อย โดยเฉพาะเมื่อข้อมูลภูมิอากาศมีขนาดใหญ่มากจนย้ายขึ้นคลาวด์ได้ไม่ง่าย และก็น่าติดตามผลการวิเคราะห์จริงในอนาคต

2 ความคิดเห็น

 
jangsc0000 2024-04-18

ความเห็นของ GN+ เป็นภาษาสุภาพเหรอครับ..?

 
GN⁺ 2024-04-17
ความเห็นจาก Hacker News

สรุปได้ดังนี้:

  • เมื่อต้องวิเคราะห์ข้อมูลเชิงพื้นที่ สิ่งสำคัญคือต้องเข้าใจระบบพิกัด (CRS) และการฉายแผนที่ สำหรับงานเชิงพื้นที่ขนาดใหญ่ Google BigQuery ทำได้โดดเด่นที่สุด

  • ต้องทดลองจึงจะรู้ว่าฐานข้อมูลเชิงสัมพันธ์เหมาะกับข้อมูลอากาศแบบกริดหรือไม่

  • สาเหตุที่ Hypertable ใน Timescale ช้า อาจมาจากดัชนีของคอลัมน์ timestamp ที่ถูกสร้างเป็นค่าเริ่มต้น ควรข้ามการสร้างดัชนีด้วยตัวเลือก create_default_indexes=>false หรือสร้างดัชนีหลังจากนำเข้าข้อมูลแล้ว

  • การวิเคราะห์ยังไม่เพียงพอว่าการย้ายข้อมูลอากาศไปยัง RDBMS มีข้อดีอะไรบ้าง ใช้ Serverless + object storage ก็ให้ความเร็วในการตอบสนองที่สูงมากได้เช่นกัน

  • ชุดข้อมูลอากาศ/ภูมิอากาศส่วนใหญ่ เช่น ERA5 ประกอบด้วยกริดละติจูด-ลองจิจูดที่เป็นระเบียบอยู่แล้ว ดังนั้นไม่ควรทำลายโครงสร้างนั้นทั้งหมด และควรใช้เวอร์ชันที่ปรับให้เหมาะกับคลาวด์อย่าง ARCO-ERA5 แทน

  • หากปิด WAL ใน PostgreSQL และรันคำสั่ง VACUUM FREEZE เป็นระยะ ก็อาจเพิ่มประสิทธิภาพการโหลดข้อมูลปริมาณมากได้อีก

  • หากใช้ COPY ไม่ได้ การเข้ารหัสแถวข้อมูลเป็นสตริง JSON แล้วส่งเป็นพารามิเตอร์ของคิวรีเดียว พร้อมใช้ json_to_recordset ก็เป็นวิธีที่ดีเช่นกัน