แยกวิเคราะห์ 25TB โดยใช้ AWK และ R

แยกวิเคราะห์ 25TB โดยใช้ AWK และ R
วิธีอ่านบทความนี้: ฉันขอโทษสำหรับข้อความที่ยาวและวุ่นวายมาก เพื่อช่วยคุณประหยัดเวลา ฉันจะเริ่มแต่ละบทด้วยคำนำ "สิ่งที่ฉันเรียนรู้" ซึ่งจะสรุปสาระสำคัญของบทด้วยหนึ่งหรือสองประโยค

“แสดงวิธีแก้ปัญหาให้ฉันดูสิ!” หากคุณต้องการดูว่าฉันมาจากไหน ให้ข้ามไปที่บท “มีความคิดสร้างสรรค์มากขึ้น” แต่ฉันคิดว่ามันน่าสนใจและมีประโยชน์มากกว่าถ้าอ่านเกี่ยวกับความล้มเหลว

เมื่อเร็วๆ นี้ ฉันได้รับมอบหมายให้ตั้งค่ากระบวนการสำหรับประมวลผลลำดับ DNA ดิบจำนวนมาก (ในทางเทคนิคแล้วคือชิป SNP) ความจำเป็นคือการได้รับข้อมูลเกี่ยวกับตำแหน่งทางพันธุกรรมที่กำหนดอย่างรวดเร็ว (เรียกว่า SNP) สำหรับการสร้างแบบจำลองในภายหลังและงานอื่น ๆ การใช้ R และ AWK ช่วยให้ฉันสามารถล้างและจัดระเบียบข้อมูลได้อย่างเป็นธรรมชาติ ซึ่งช่วยเร่งการประมวลผลคำค้นหาได้อย่างมาก นี่ไม่ใช่เรื่องง่ายสำหรับฉันและต้องทำซ้ำหลายครั้ง บทความนี้จะช่วยคุณหลีกเลี่ยงข้อผิดพลาดบางอย่างของฉันและแสดงให้คุณเห็นว่าฉันลงเอยอย่างไร

ขั้นแรกให้อธิบายเบื้องต้นบางประการ

ข้อมูล

ศูนย์ประมวลผลข้อมูลทางพันธุกรรมของมหาวิทยาลัยของเราให้ข้อมูลในรูปแบบของ TSV ขนาด 25 TB ฉันได้รับมันแบ่งออกเป็น 5 แพ็คเกจ ซึ่งบีบอัดโดย Gzip แต่ละแพ็คเกจมีไฟล์ขนาดสี่กิกะไบต์ประมาณ 240 ไฟล์ แต่ละแถวมีข้อมูลสำหรับหนึ่ง SNP จากบุคคลหนึ่งราย โดยรวมแล้วมีการส่งข้อมูล SNP ประมาณ 2,5 ล้าน SNP และผู้คนประมาณ 60 คน นอกเหนือจากข้อมูล SNP แล้ว ไฟล์ดังกล่าวยังมีคอลัมน์จำนวนมากที่มีตัวเลขที่สะท้อนถึงคุณลักษณะต่างๆ เช่น ความเข้มในการอ่าน ความถี่ของอัลลีลที่แตกต่างกัน เป็นต้น โดยรวมแล้วมีประมาณ 30 คอลัมน์ที่มีค่าไม่ซ้ำกัน

เป้า

เช่นเดียวกับโครงการจัดการข้อมูลอื่นๆ สิ่งที่สำคัญที่สุดคือการกำหนดว่าข้อมูลจะถูกนำไปใช้อย่างไร ในกรณีนี้ ส่วนใหญ่เราจะเลือกโมเดลและเวิร์กโฟลว์สำหรับ SNP ตาม SNP. นั่นคือเราต้องการข้อมูลบน SNP เดียวเท่านั้นในแต่ละครั้ง ฉันต้องเรียนรู้วิธีเรียกค้นบันทึกทั้งหมดที่เกี่ยวข้องกับ SNP หนึ่งใน 2,5 ล้านรายการอย่างง่ายดาย รวดเร็ว และถูกที่สุดเท่าที่จะเป็นไปได้

จะไม่ทำเช่นนี้ได้อย่างไร

หากต้องการอ้างอิงความคิดโบราณที่เหมาะสม:

ฉันไม่ได้ล้มเหลวเป็นพันครั้ง ฉันเพิ่งค้นพบวิธีการนับพันครั้งในการหลีกเลี่ยงการแยกวิเคราะห์ข้อมูลจำนวนมากในรูปแบบที่เป็นมิตรกับการค้นหา

ครั้งแรกลอง

ฉันได้เรียนรู้อะไรบ้าง: ไม่มีวิธีที่ถูกในการแยกวิเคราะห์ครั้งละ 25 TB

หลังจากเรียนหลักสูตร "วิธีการขั้นสูงสำหรับการประมวลผลข้อมูลขนาดใหญ่" ที่มหาวิทยาลัย Vanderbilt ฉันแน่ใจว่าเคล็ดลับอยู่ในกระเป๋า อาจต้องใช้เวลาหนึ่งหรือสองชั่วโมงในการตั้งค่าเซิร์ฟเวอร์ Hive เพื่อให้ทำงานผ่านข้อมูลทั้งหมดและรายงานผลลัพธ์ เนื่องจากข้อมูลของเราถูกจัดเก็บไว้ใน AWS S3 ฉันจึงใช้บริการนี้ Athenaซึ่งช่วยให้คุณสามารถใช้การสืบค้น Hive SQL กับข้อมูล S3 ได้ คุณไม่จำเป็นต้องตั้งค่า/เพิ่มคลัสเตอร์ Hive และคุณยังจ่ายเฉพาะข้อมูลที่คุณกำลังมองหาเท่านั้น

หลังจากที่ฉันแสดงข้อมูลและรูปแบบของ Athena ฉันก็ได้ทำการทดสอบบางอย่างกับข้อความค้นหาดังนี้:

select * from intensityData limit 10;

และได้รับผลลัพธ์ที่มีโครงสร้างดีอย่างรวดเร็ว พร้อม.

จนเราลองใช้ข้อมูลในการทำงาน...

ฉันถูกขอให้ดึงข้อมูล SNP ทั้งหมดออกมาเพื่อทดสอบโมเดล ฉันรันแบบสอบถาม:


select * from intensityData 
where snp = 'rs123456';

...และเริ่มรอ หลังจากแปดนาทีและข้อมูลที่ร้องขอมากกว่า 4 TB ฉันก็ได้รับผลลัพธ์ Athena คิดค่าบริการตามปริมาณข้อมูลที่พบ 5 ดอลลาร์ต่อเทราไบต์ ดังนั้นคำขอเดียวนี้จึงมีค่าใช้จ่าย 20 ดอลลาร์และใช้เวลารอแปดนาที ในการรันโมเดลกับข้อมูลทั้งหมด เราต้องรอ 38 ปีและจ่ายเงิน 50 ล้านดอลลาร์ แน่นอนว่าสิ่งนี้ไม่เหมาะกับเรา

จำเป็นต้องใช้ไม้ปาร์เก้...

ฉันได้เรียนรู้อะไรบ้าง: ระวังขนาดของไฟล์ Parquet ของคุณและการจัดวาง

ก่อนอื่นฉันพยายามแก้ไขสถานการณ์โดยแปลง TSV ทั้งหมดเป็น ไฟล์ปาร์เก้. สะดวกสำหรับการทำงานกับชุดข้อมูลขนาดใหญ่เนื่องจากข้อมูลในนั้นถูกจัดเก็บในรูปแบบคอลัมน์: แต่ละคอลัมน์อยู่ในหน่วยความจำ/ส่วนของดิสก์ของตัวเอง ตรงกันข้ามกับไฟล์ข้อความ ซึ่งแถวมีองค์ประกอบของแต่ละคอลัมน์ และหากคุณต้องการค้นหาบางสิ่งก็แค่อ่านคอลัมน์ที่ต้องการ นอกจากนี้ แต่ละไฟล์จะจัดเก็บช่วงของค่าไว้ในคอลัมน์ ดังนั้นหากค่าที่คุณกำลังมองหาไม่อยู่ในช่วงของคอลัมน์ Spark จะไม่เสียเวลาในการสแกนทั้งไฟล์

ฉันทำงานง่ายๆ AWS กาว เพื่อแปลง TSV ของเราเป็น Parquet และวางไฟล์ใหม่ลงใน Athena ใช้เวลาประมาณ 5 ชั่วโมง แต่เมื่อฉันดำเนินการตามคำขอ ก็ใช้เวลาประมาณเท่าเดิมและใช้เงินน้อยกว่าเล็กน้อยในการดำเนินการให้เสร็จสิ้น ความจริงก็คือ Spark พยายามเพิ่มประสิทธิภาพงาน เพียงแกะชิ้นส่วน TSV หนึ่งชิ้นแล้วใส่ลงในชิ้นไม้ปาร์เก้ของตัวเอง และเนื่องจากแต่ละชิ้นมีขนาดใหญ่พอที่จะบรรจุบันทึกทั้งหมดของบุคคลจำนวนมาก แต่ละไฟล์จึงมี SNP ทั้งหมด ดังนั้น Spark จึงต้องเปิดไฟล์ทั้งหมดเพื่อดึงข้อมูลที่ต้องการ

สิ่งที่น่าสนใจคือประเภทการบีบอัดเริ่มต้น (และที่แนะนำ) ของ Parquet ซึ่งเร็วนั้นไม่สามารถแบ่งแยกได้ ดังนั้นผู้ดำเนินการแต่ละคนจึงติดอยู่กับงานในการคลายแพ็กและดาวน์โหลดชุดข้อมูลขนาด 3,5 GB ทั้งหมด

แยกวิเคราะห์ 25TB โดยใช้ AWK และ R

มาทำความเข้าใจปัญหากัน

ฉันได้เรียนรู้อะไรบ้าง: การเรียงลำดับทำได้ยาก โดยเฉพาะหากมีการกระจายข้อมูล

สำหรับฉันดูเหมือนว่าตอนนี้ฉันเข้าใจสาระสำคัญของปัญหาแล้ว ฉันจำเป็นต้องจัดเรียงข้อมูลตามคอลัมน์ SNP เท่านั้น ไม่ใช่ตามบุคคล จากนั้น SNP หลายรายการจะถูกจัดเก็บไว้ในกลุ่มข้อมูลที่แยกจากกัน จากนั้นฟังก์ชัน “อัจฉริยะ” ของ Parquet “เปิดเฉพาะเมื่อค่าอยู่ในช่วง” จะแสดงตัวเองออกมาอย่างสง่างาม น่าเสียดายที่การจัดเรียงแถวนับพันล้านแถวที่กระจัดกระจายไปทั่วคลัสเตอร์กลายเป็นงานที่ยาก

AWS ไม่ต้องการคืนเงินอย่างแน่นอนเนื่องจากเหตุผล "ฉันเป็นนักเรียนที่ฟุ้งซ่าน" หลังจากที่ฉันรันการเรียงลำดับบน Amazon Glue มันก็ทำงานเป็นเวลา 2 วันและเกิดข้อขัดข้อง

แล้วการแบ่งพาร์ติชั่นล่ะ?

ฉันได้เรียนรู้อะไรบ้าง: พาร์ติชันใน Spark จะต้องมีความสมดุล

จากนั้นฉันก็เกิดแนวคิดในการแบ่งพาร์ติชันข้อมูลในโครโมโซม มี 23 รายการ (และอีกหลายแห่งหากคุณคำนึงถึง DNA ของไมโตคอนเดรียและบริเวณที่ไม่ได้แมป)
ซึ่งจะทำให้คุณสามารถแบ่งข้อมูลออกเป็นส่วนเล็กๆ ได้ หากคุณเพิ่มเพียงหนึ่งบรรทัดในฟังก์ชันการส่งออก Spark ในสคริปต์กาว partition_by = "chr"จากนั้นข้อมูลควรแบ่งออกเป็นที่เก็บข้อมูล

แยกวิเคราะห์ 25TB โดยใช้ AWK และ R
จีโนมประกอบด้วยชิ้นส่วนจำนวนมากที่เรียกว่าโครโมโซม

น่าเสียดายที่มันไม่ได้ผล โครโมโซมมีขนาดแตกต่างกัน ซึ่งหมายถึงปริมาณข้อมูลที่แตกต่างกัน ซึ่งหมายความว่างานที่ Spark ส่งถึงผู้ปฏิบัติงานไม่สมดุลและเสร็จสิ้นช้า เนื่องจากบางโหนดเสร็จเร็วและไม่ได้ใช้งาน อย่างไรก็ตาม งานต่างๆ ก็เสร็จสิ้นแล้ว แต่เมื่อขอ SNP หนึ่งรายการ ความไม่สมดุลกลับทำให้เกิดปัญหาอีกครั้ง ค่าใช้จ่ายในการประมวลผล SNP บนโครโมโซมที่มีขนาดใหญ่กว่า (นั่นคือจุดที่เราต้องการรับข้อมูล) ลดลงเพียงประมาณ 10 เท่าเท่านั้น เยอะแต่ยังไม่พอ

จะเป็นอย่างไรถ้าเราแบ่งมันออกเป็นส่วนย่อยๆ ล่ะ?

ฉันได้เรียนรู้อะไรบ้าง: อย่าพยายามสร้างพาร์ติชั่น 2,5 ล้านพาร์ติชั่นเลย

ฉันตัดสินใจออกไปทั้งหมดและแบ่งพาร์ติชัน SNP แต่ละรายการ เพื่อให้แน่ใจว่าพาร์ติชันมีขนาดเท่ากัน มันเป็นความคิดที่ไม่ดี. ฉันใช้กาวและเพิ่มบรรทัดที่ไร้เดียงสา partition_by = 'snp'. งานเริ่มต้นและเริ่มดำเนินการ วันต่อมาฉันตรวจสอบและเห็นว่ายังไม่มีสิ่งใดเขียนถึง S3 ดังนั้นฉันจึงปิดงานนั้น ดูเหมือนว่า Glue กำลังเขียนไฟล์ระดับกลางไปยังตำแหน่งที่ซ่อนอยู่ใน S3 ซึ่งเป็นไฟล์จำนวนมากหรืออาจเป็นสองสามล้านไฟล์ เป็นผลให้ความผิดพลาดของฉันมีค่าใช้จ่ายมากกว่าหนึ่งพันดอลลาร์และไม่ได้ทำให้ที่ปรึกษาของฉันพอใจ

การแบ่งพาร์ติชัน + การเรียงลำดับ

ฉันได้เรียนรู้อะไรบ้าง: การเรียงลำดับยังคงทำได้ยาก เช่นเดียวกับการปรับ Spark

ความพยายามครั้งสุดท้ายของฉันในการแบ่งพาร์ติชันเกี่ยวข้องกับการแบ่งพาร์ติชันโครโมโซมแล้วจึงเรียงลำดับแต่ละพาร์ติชัน ตามทฤษฎีแล้ว สิ่งนี้จะช่วยเร่งความเร็วในการสืบค้นแต่ละครั้ง เนื่องจากข้อมูล SNP ที่ต้องการต้องอยู่ภายในชิ้นไม้ปาร์เก้สองสามชิ้นภายในช่วงที่กำหนด น่าเสียดายที่การจัดเรียงข้อมูลที่แบ่งพาร์ติชันกลายเป็นงานที่ยาก ด้วยเหตุนี้ ฉันจึงเปลี่ยนมาใช้ EMR สำหรับคลัสเตอร์แบบกำหนดเอง และใช้อินสแตนซ์ที่ทรงพลังแปดอินสแตนซ์ (C5.4xl) และ Sparklyr เพื่อสร้างเวิร์กโฟลว์ที่ยืดหยุ่นมากขึ้น...

# Sparklyr snippet to partition by chr and sort w/in partition
# Join the raw data with the snp bins
raw_data
  group_by(chr) %>%
  arrange(Position) %>% 
  Spark_write_Parquet(
    path = DUMP_LOC,
    mode = 'overwrite',
    partition_by = c('chr')
  )

...แต่งานก็ยังไม่เสร็จสิ้น ฉันกำหนดค่าด้วยวิธีต่างๆ: เพิ่มการจัดสรรหน่วยความจำสำหรับตัวดำเนินการแบบสอบถามแต่ละตัว, ใช้โหนดที่มีหน่วยความจำจำนวนมาก, ใช้ตัวแปรการออกอากาศ (ตัวแปรการออกอากาศ) แต่แต่ละครั้งสิ่งเหล่านี้กลายเป็นเพียงครึ่งวัด และค่อยๆ ตัวดำเนินการเริ่มต้นขึ้น ล้มเหลวจนกว่าทุกอย่างจะหยุดลง

ฉันมีความคิดสร้างสรรค์มากขึ้น

ฉันได้เรียนรู้อะไรบ้าง: บางครั้งข้อมูลพิเศษต้องใช้วิธีแก้ไขปัญหาพิเศษ

SNP แต่ละรายการมีค่าตำแหน่ง นี่คือตัวเลขที่สอดคล้องกับจำนวนฐานตามโครโมโซม นี่เป็นวิธีที่ดีและเป็นธรรมชาติในการจัดระเบียบข้อมูลของเรา ตอนแรกฉันต้องการแบ่งส่วนตามบริเวณของแต่ละโครโมโซม เช่น ตำแหน่ง 1 - 2000, 2001 - 4000 เป็นต้น แต่ปัญหาคือ SNP ไม่กระจายทั่วทั้งโครโมโซมเท่าๆ กัน ดังนั้นขนาดของกลุ่มจึงแตกต่างกันมาก

แยกวิเคราะห์ 25TB โดยใช้ AWK และ R

เป็นผลให้ฉันแบ่งตำแหน่งออกเป็นหมวดหมู่ (อันดับ) โดยใช้ข้อมูลที่ดาวน์โหลดไปแล้ว ฉันได้ดำเนินการร้องขอเพื่อรับรายการ SNP ที่ไม่ซ้ำกัน ตำแหน่ง และโครโมโซม จากนั้น ฉันจัดเรียงข้อมูลภายในโครโมโซมแต่ละตัวและรวบรวม SNP ออกเป็นกลุ่ม (bin) ตามขนาดที่กำหนด สมมติว่าแต่ละ 1000 SNP สิ่งนี้ทำให้ฉันมีความสัมพันธ์ SNP-to-group-per-chromosome

ในท้ายที่สุด ฉันได้สร้างกลุ่ม (bin) จำนวน 75 SNP เหตุผลจะอธิบายไว้ด้านล่าง

snp_to_bin <- unique_snps %>% 
  group_by(chr) %>% 
  arrange(position) %>% 
  mutate(
    rank = 1:n()
    bin = floor(rank/snps_per_bin)
  ) %>% 
  ungroup()

ลองใช้ Spark ครั้งแรก

ฉันได้เรียนรู้อะไรบ้าง: การรวม Spark รวดเร็ว แต่การแบ่งพาร์ติชันยังคงมีราคาแพง

ฉันต้องการอ่านเฟรมข้อมูลขนาดเล็ก (2,5 ล้านแถว) นี้ลงใน Spark รวมเข้ากับข้อมูลดิบ จากนั้นแบ่งพาร์ติชันตามคอลัมน์ที่เพิ่มใหม่ bin.


# Join the raw data with the snp bins
data_w_bin <- raw_data %>%
  left_join(sdf_broadcast(snp_to_bin), by ='snp_name') %>%
  group_by(chr_bin) %>%
  arrange(Position) %>% 
  Spark_write_Parquet(
    path = DUMP_LOC,
    mode = 'overwrite',
    partition_by = c('chr_bin')
  )

ฉันใช้ sdf_broadcast()ดังนั้น Spark จึงรู้ว่าควรส่ง data frame ไปยังโหนดทั้งหมด สิ่งนี้มีประโยชน์หากข้อมูลมีขนาดเล็กและจำเป็นสำหรับงานทั้งหมด มิฉะนั้น Spark จะพยายามฉลาดและกระจายข้อมูลตามความจำเป็น ซึ่งอาจทำให้เกิดการชะลอตัวได้

และอีกครั้งที่ความคิดของฉันไม่ได้ผล: งานต่างๆ ได้ผลมาระยะหนึ่งแล้ว สหภาพแรงงานเสร็จสมบูรณ์ และจากนั้น เช่นเดียวกับผู้ดำเนินการที่เริ่มโดยการแบ่งพาร์ติชัน พวกเขาก็เริ่มล้มเหลว

กำลังเพิ่ม AWK

ฉันได้เรียนรู้อะไรบ้าง: อย่านอนเมื่อคุณถูกสอนพื้นฐาน แน่นอนว่ามีคนแก้ไขปัญหาของคุณไปแล้วในช่วงทศวรรษ 1980

จนถึงจุดนี้ สาเหตุของความล้มเหลวทั้งหมดของฉันกับ Spark ก็คือข้อมูลในคลัสเตอร์ที่สับสน บางทีสถานการณ์อาจดีขึ้นได้ด้วยการรักษาล่วงหน้า ฉันตัดสินใจลองแยกข้อมูลข้อความดิบออกเป็นคอลัมน์ของโครโมโซม ดังนั้นฉันจึงหวังว่าจะให้ข้อมูล "แบ่งพาร์ติชันล่วงหน้า" แก่ Spark

ฉันค้นหา StackOverflow เพื่อดูวิธีแยกตามค่าคอลัมน์และพบ เป็นคำตอบที่ยอดเยี่ยมมาก ด้วย AWK คุณสามารถแบ่งไฟล์ข้อความตามค่าคอลัมน์โดยการเขียนลงในสคริปต์แทนที่จะส่งผลลัพธ์ไป stdout.

ฉันเขียนสคริปต์ Bash เพื่อทดลองใช้ ดาวน์โหลด TSV แพ็กเกจหนึ่ง จากนั้นแตกไฟล์โดยใช้ gzip และส่งไปที่ awk.

gzip -dc path/to/chunk/file.gz |
awk -F 't' 
'{print $1",..."$30">"chunked/"$chr"_chr"$15".csv"}'

มันได้ผล!

เติมแกน

ฉันได้เรียนรู้อะไรบ้าง: gnu parallel - มันเป็นสิ่งมหัศจรรย์ที่ทุกคนควรใช้มัน

การแยกทางกันค่อนข้างช้าและเมื่อฉันเริ่มต้น htopเพื่อตรวจสอบการใช้ EC2 instance ที่ทรงพลัง (และมีราคาแพง) ปรากฎว่าฉันใช้เพียงคอร์เดียวและมีหน่วยความจำประมาณ 200 MB เพื่อแก้ปัญหาและไม่เสียเงินมากมาย เราต้องคิดหาวิธีการทำงานแบบขนาน โชคดีที่มีหนังสือที่น่าทึ่งอย่างยิ่ง วิทยาศาสตร์ข้อมูลที่บรรทัดคำสั่ง ฉันพบบทหนึ่งของ Jeron Janssens เกี่ยวกับการขนาน จากนั้นฉันก็ได้เรียนรู้เกี่ยวกับ gnu parallelซึ่งเป็นวิธีการที่ยืดหยุ่นมากสำหรับการใช้งานมัลติเธรดใน Unix

แยกวิเคราะห์ 25TB โดยใช้ AWK และ R
เมื่อฉันเริ่มการแบ่งพาร์ติชันโดยใช้กระบวนการใหม่ ทุกอย่างเรียบร้อยดี แต่ยังมีปัญหาคอขวดอยู่ - การดาวน์โหลดออบเจ็กต์ S3 ลงดิสก์นั้นไม่เร็วมากและไม่ได้ขนานอย่างสมบูรณ์ เพื่อแก้ไขปัญหานี้ ฉันทำสิ่งนี้:

  1. ฉันพบว่าเป็นไปได้ที่จะใช้ขั้นตอนการดาวน์โหลด S3 โดยตรงในไปป์ไลน์ โดยกำจัดที่เก็บข้อมูลระดับกลางบนดิสก์โดยสิ้นเชิง ซึ่งหมายความว่าฉันสามารถหลีกเลี่ยงการเขียนข้อมูลดิบลงดิสก์และใช้พื้นที่จัดเก็บบน AWS ที่มีขนาดเล็กกว่าและราคาถูกกว่าได้
  2. ทีม aws configure set default.s3.max_concurrent_requests 50 เพิ่มจำนวนเธรดที่ AWS CLI ใช้อย่างมาก (โดยค่าเริ่มต้นคือ 10)
  3. ฉันเปลี่ยนไปใช้อินสแตนซ์ EC2 ที่ปรับให้เหมาะกับความเร็วเครือข่าย โดยมีตัวอักษร n อยู่ในชื่อ ฉันพบว่าการสูญเสียพลังการประมวลผลเมื่อใช้ n-instance นั้นมากกว่าการชดเชยด้วยการเพิ่มความเร็วในการโหลด สำหรับงานส่วนใหญ่ฉันใช้ c5n.4xl
  4. เปลี่ยน gzip บน pigzนี่เป็นเครื่องมือ gzip ที่สามารถทำสิ่งดีๆ เพื่อทำให้งานคลายการบีบอัดไฟล์ที่ไม่ขนานในตอนแรก (ซึ่งช่วยได้น้อยที่สุด)

# Let S3 use as many threads as it wants
aws configure set default.s3.max_concurrent_requests 50

for chunk_file in $(aws s3 ls $DATA_LOC | awk '{print $4}' | grep 'chr'$DESIRED_CHR'.csv') ; do

        aws s3 cp s3://$batch_loc$chunk_file - |
        pigz -dc |
        parallel --block 100M --pipe  
        "awk -F 't' '{print $1",..."$30">"chunked/{#}_chr"$15".csv"}'"

       # Combine all the parallel process chunks to single files
        ls chunked/ |
        cut -d '_' -f 2 |
        sort -u |
        parallel 'cat chunked/*_{} | sort -k5 -n -S 80% -t, | aws s3 cp - '$s3_dest'/batch_'$batch_num'_{}'
        
         # Clean up intermediate data
       rm chunked/*
done

ขั้นตอนเหล่านี้จะรวมเข้าด้วยกันเพื่อให้ทุกอย่างทำงานได้อย่างรวดเร็ว ด้วยการเพิ่มความเร็วในการดาวน์โหลดและกำจัดการเขียนดิสก์ ตอนนี้ฉันสามารถประมวลผลแพ็คเกจขนาด 5 เทราไบต์ได้ในเวลาเพียงไม่กี่ชั่วโมง

ทวีตนี้น่าจะพูดถึง 'TSV' อนิจจา.

การใช้ข้อมูลที่แยกวิเคราะห์ใหม่

ฉันได้เรียนรู้อะไรบ้าง: Spark ชอบข้อมูลที่ไม่มีการบีบอัดและไม่ชอบการรวมพาร์ติชัน

ตอนนี้ข้อมูลอยู่ใน S3 ในรูปแบบคลายแพ็ก (อ่าน: แชร์) และรูปแบบกึ่งเรียงลำดับ และฉันสามารถกลับสู่ Spark อีกครั้งได้ ความประหลาดใจรอฉันอยู่: ฉันล้มเหลวอีกครั้งในการบรรลุสิ่งที่ต้องการ! เป็นเรื่องยากมากที่จะบอก Spark อย่างชัดเจนว่าข้อมูลถูกแบ่งพาร์ติชันอย่างไร และแม้ว่าฉันจะทำสิ่งนี้ปรากฎว่ามีพาร์ติชั่นมากเกินไป (95) และเมื่อฉันใช้ coalesce ลดจำนวนลงเหลือขีดจำกัดที่สมเหตุสมผล สิ่งนี้ทำลายการแบ่งพาร์ติชันของฉัน ฉันแน่ใจว่าสิ่งนี้สามารถแก้ไขได้ แต่หลังจากค้นหาสองสามวันฉันก็ไม่พบวิธีแก้ไข ในที่สุดฉันก็ทำงานทั้งหมดใน Spark สำเร็จ แม้ว่าจะต้องใช้เวลาสักพักและไฟล์ Parquet ที่แยกออกมาก็ไม่เล็กมาก (~200 KB) อย่างไรก็ตาม ข้อมูลอยู่ในจุดที่จำเป็น

แยกวิเคราะห์ 25TB โดยใช้ AWK และ R
เล็กเกินไปและไม่สม่ำเสมอ ยอดเยี่ยมมาก!

การทดสอบคำสั่ง Spark ในเครื่อง

ฉันได้เรียนรู้อะไรบ้าง: Spark มีค่าใช้จ่ายมากเกินไปเมื่อแก้ไขปัญหาง่ายๆ

ด้วยการดาวน์โหลดข้อมูลในรูปแบบที่ชาญฉลาด ฉันสามารถทดสอบความเร็วได้ ตั้งค่าสคริปต์ R เพื่อเรียกใช้เซิร์ฟเวอร์ Spark ในเครื่อง จากนั้นโหลดเฟรมข้อมูล Spark จากที่เก็บข้อมูลกลุ่ม Parquet (ถัง) ที่ระบุ ฉันพยายามโหลดข้อมูลทั้งหมดแต่ไม่สามารถทำให้ Sparklyr จดจำการแบ่งพาร์ติชันได้

sc <- Spark_connect(master = "local")

desired_snp <- 'rs34771739'

# Start a timer
start_time <- Sys.time()

# Load the desired bin into Spark
intensity_data <- sc %>% 
  Spark_read_Parquet(
    name = 'intensity_data', 
    path = get_snp_location(desired_snp),
    memory = FALSE )

# Subset bin to snp and then collect to local
test_subset <- intensity_data %>% 
  filter(SNP_Name == desired_snp) %>% 
  collect()

print(Sys.time() - start_time)

การดำเนินการใช้เวลา 29,415 วินาที ดีกว่ามาก แต่ก็ไม่ดีเกินไปสำหรับการทดสอบอะไรก็ตาม นอกจากนี้ ฉันไม่สามารถเร่งความเร็วสิ่งต่าง ๆ ด้วยการแคชได้ เพราะเมื่อฉันพยายามแคชเฟรมข้อมูลในหน่วยความจำ Spark จะขัดข้องอยู่เสมอ แม้ว่าฉันจะจัดสรรหน่วยความจำมากกว่า 50 GB ให้กับชุดข้อมูลที่มีน้ำหนักน้อยกว่า 15 ก็ตาม

กลับไปที่ AWK

ฉันได้เรียนรู้อะไรบ้าง: อาร์เรย์แบบเชื่อมโยงใน AWK มีประสิทธิภาพมาก

ฉันตระหนักว่าฉันสามารถบรรลุความเร็วที่สูงขึ้นได้ ฉันจำได้ว่าในความมหัศจรรย์ บทช่วยสอน AWK โดย Bruce Barnett ฉันอ่านเกี่ยวกับฟีเจอร์เจ๋งๆ ที่เรียกว่า “อาร์เรย์ที่เชื่อมโยง" โดยพื้นฐานแล้ว คู่เหล่านี้คือคู่คีย์-ค่า ซึ่งด้วยเหตุผลบางประการจึงถูกเรียกแตกต่างกันใน AWK ดังนั้นฉันจึงไม่ได้คิดมากเกี่ยวกับพวกเขา โรมัน เชพยากา จำได้ว่าคำว่า "อาเรย์แบบเชื่อมโยง" นั้นเก่ากว่าคำว่า "คู่คีย์-ค่า" มาก แม้ว่าคุณจะ ค้นหาคีย์-ค่าใน Google Ngramคุณจะไม่เห็นคำนี้ตรงนั้น แต่คุณจะพบอาร์เรย์ที่เชื่อมโยง! นอกจากนี้ “คู่คีย์-ค่า” มักเชื่อมโยงกับฐานข้อมูลมากที่สุด ดังนั้นจึงสมเหตุสมผลกว่ามากที่จะเปรียบเทียบกับแฮชแมป ฉันรู้ว่าฉันสามารถใช้อาเรย์เชื่อมโยงเหล่านี้เพื่อเชื่อมโยง SNP ของฉันกับตารางบินและข้อมูลดิบได้โดยไม่ต้องใช้ Spark

ในการทำเช่นนี้ฉันใช้บล็อกในสคริปต์ AWK BEGIN. นี่คือโค้ดชิ้นหนึ่งที่ดำเนินการก่อนที่จะส่งข้อมูลบรรทัดแรกไปยังเนื้อหาหลักของสคริปต์

join_data.awk
BEGIN {
  FS=",";
  batch_num=substr(chunk,7,1);
  chunk_id=substr(chunk,15,2);
  while(getline < "snp_to_bin.csv") {bin[$1] = $2}
}
{
  print $0 > "chunked/chr_"chr"_bin_"bin[$1]"_"batch_num"_"chunk_id".csv"
}

ทีม while(getline...) โหลดแถวทั้งหมดจากกลุ่ม CSV (bin) ตั้งค่าคอลัมน์แรก (ชื่อ SNP) เป็นคีย์สำหรับอาร์เรย์ที่เชื่อมโยง bin และค่าที่สอง (กลุ่ม) เป็นค่า แล้วในบล็อค. { }ซึ่งดำเนินการกับทุกบรรทัดของไฟล์หลัก แต่ละบรรทัดจะถูกส่งไปยังไฟล์เอาต์พุต ซึ่งได้รับชื่อที่ไม่ซ้ำกันขึ้นอยู่กับกลุ่ม (bin): ..._bin_"bin[$1]"_....

ตัวแปร batch_num и chunk_id ตรงกับข้อมูลที่ให้โดยไปป์ไลน์ การหลีกเลี่ยงสภาวะการแข่งขัน และเธรดการประมวลผลแต่ละรายการที่ทำงานอยู่ parallelเขียนลงในไฟล์เฉพาะของตัวเอง

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

DESIRED_CHR='13'

# Download chromosome data from s3 and split into bins
aws s3 ls $DATA_LOC |
awk '{print $4}' |
grep 'chr'$DESIRED_CHR'.csv' |
parallel "echo 'reading {}'; aws s3 cp "$DATA_LOC"{} - | awk -v chr=""$DESIRED_CHR"" -v chunk="{}" -f split_on_chr_bin.awk"

# Combine all the parallel process chunks to single files and upload to rds using R
ls chunked/ |
cut -d '_' -f 4 |
sort -u |
parallel "echo 'zipping bin {}'; cat chunked/*_bin_{}_*.csv | ./upload_as_rds.R '$S3_DEST'/chr_'$DESIRED_CHR'_bin_{}.rds"
rm chunked/*

สคริปต์มีสองส่วน parallel.

ในส่วนแรก ข้อมูลจะถูกอ่านจากไฟล์ทั้งหมดที่มีข้อมูลเกี่ยวกับโครโมโซมที่ต้องการ จากนั้นข้อมูลนี้จะถูกกระจายไปตามเธรด ซึ่งกระจายไฟล์ออกเป็นกลุ่ม (bin) ที่เหมาะสม เพื่อหลีกเลี่ยงสภาวะการแข่งขันเมื่อหลายเธรดเขียนลงในไฟล์เดียวกัน AWK จะส่งชื่อไฟล์เพื่อเขียนข้อมูลไปยังที่ต่างๆ เช่น chr_10_bin_52_batch_2_aa.csv. เป็นผลให้มีการสร้างไฟล์ขนาดเล็กจำนวนมากบนดิสก์ (สำหรับสิ่งนี้ฉันใช้โวลุ่ม EBS เทราไบต์)

สายพานลำเลียงจากส่วนที่สอง parallel ผ่านกลุ่ม (bin) และรวมไฟล์แต่ละไฟล์เป็น CSV ทั่วไป catแล้วจึงส่งออกไป

ออกอากาศใน R?

ฉันได้เรียนรู้อะไรบ้าง: สามารถติดต่อได้ stdin и stdout จากสคริปต์ R และใช้ในไปป์ไลน์

คุณอาจสังเกตเห็นบรรทัดนี้ในสคริปต์ Bash ของคุณ: ...cat chunked/*_bin_{}_*.csv | ./upload_as_rds.R.... โดยจะแปลไฟล์กลุ่มที่ต่อกัน (bin) ทั้งหมดเป็นสคริปต์ R ด้านล่าง {} เป็นเทคนิคพิเศษ parallelซึ่งจะแทรกข้อมูลใดๆ ที่ส่งไปยังสตรีมที่ระบุลงในคำสั่งโดยตรง ตัวเลือก {#} จัดเตรียม ID เธรดที่ไม่ซ้ำใคร และ {%} แสดงถึงหมายเลขช่องงาน (ซ้ำแต่ไม่เคยพร้อมกัน) รายการตัวเลือกทั้งหมดสามารถพบได้ใน เอกสาร

#!/usr/bin/env Rscript
library(readr)
library(aws.s3)

# Read first command line argument
data_destination <- commandArgs(trailingOnly = TRUE)[1]

data_cols <- list(SNP_Name = 'c', ...)

s3saveRDS(
  read_csv(
        file("stdin"), 
        col_names = names(data_cols),
        col_types = data_cols 
    ),
  object = data_destination
)

เมื่อมีตัวแปร file("stdin") ส่งไปยัง readr::read_csvข้อมูลที่แปลเป็นสคริปต์ R จะถูกโหลดลงในเฟรม ซึ่งจากนั้นจะอยู่ในรูปแบบ .rds- การใช้ไฟล์ aws.s3 เขียนโดยตรงไปยัง S3

RDS มีลักษณะคล้ายกับ Parquet เวอร์ชันจูเนียร์ โดยไม่มีที่เก็บลำโพงที่หรูหรา

หลังจากเสร็จสิ้นสคริปต์ Bash ฉันก็ได้รับบันเดิล .rds-ไฟล์ที่อยู่ใน S3 ซึ่งช่วยให้ฉันใช้การบีบอัดและประเภทในตัวได้อย่างมีประสิทธิภาพ

แม้จะใช้เบรก R แต่ทุกอย่างก็ทำงานเร็วมาก ไม่น่าแปลกใจที่ส่วนของ R ที่อ่านและเขียนข้อมูลได้รับการปรับให้เหมาะสมที่สุด หลังจากการทดสอบบนโครโมโซมขนาดกลางหนึ่งโครโมโซม งานบนอินสแตนซ์ C5n.4xl ก็เสร็จสมบูรณ์ภายในเวลาประมาณสองชั่วโมง

ข้อจำกัดของ S3

ฉันได้เรียนรู้อะไรบ้าง: ต้องขอบคุณการใช้งานเส้นทางอัจฉริยะ S3 จึงสามารถจัดการไฟล์ได้จำนวนมาก

ฉันกังวลว่า S3 จะสามารถจัดการไฟล์จำนวนมากที่ถ่ายโอนไปได้หรือไม่ ฉันสามารถทำให้ชื่อไฟล์ดูสมเหตุสมผลได้ แต่ S3 จะค้นหามันอย่างไร

แยกวิเคราะห์ 25TB โดยใช้ AWK และ R
โฟลเดอร์ใน S3 มีไว้เพื่อแสดงเท่านั้น จริงๆ แล้วระบบไม่ได้สนใจสัญลักษณ์นี้ /. จากหน้าคำถามที่พบบ่อยของ S3

ดูเหมือนว่า S3 จะแสดงเส้นทางไปยังไฟล์ใดไฟล์หนึ่งเป็นคีย์ธรรมดาในตารางแฮชหรือฐานข้อมูลตามเอกสาร ที่เก็บข้อมูลสามารถถือเป็นตาราง และไฟล์สามารถถือเป็นบันทึกในตารางนั้นได้

เนื่องจากความเร็วและประสิทธิภาพมีความสำคัญต่อการทำกำไรที่ Amazon จึงไม่น่าแปลกใจที่ระบบคีย์เป็นไฟล์พาธนี้ได้รับการปรับให้เหมาะสมอย่างยิ่ง ฉันพยายามหาจุดสมดุล: เพื่อที่ฉันจะได้ไม่ต้องส่งคำขอจำนวนมาก แต่คำขอได้รับการดำเนินการอย่างรวดเร็ว ปรากฎว่าวิธีที่ดีที่สุดคือสร้างไฟล์ bin ประมาณ 20 ไฟล์ ฉันคิดว่าหากเราเพิ่มประสิทธิภาพต่อไป เราจะสามารถบรรลุความเร็วที่เพิ่มขึ้นได้ (เช่น การสร้างที่เก็บข้อมูลพิเศษเฉพาะสำหรับข้อมูล ซึ่งจะช่วยลดขนาดของตารางการค้นหา) แต่ไม่มีเวลาหรือเงินสำหรับการทดลองเพิ่มเติม

แล้วความเข้ากันได้ข้ามล่ะ?

สิ่งที่ฉันเรียนรู้: สาเหตุอันดับหนึ่งของการเสียเวลาคือการปรับวิธีการจัดเก็บข้อมูลให้เหมาะสมก่อนเวลาอันควร

ณ จุดนี้ สิ่งสำคัญมากคือต้องถามตัวเองว่า “เหตุใดจึงต้องใช้รูปแบบไฟล์ที่เป็นกรรมสิทธิ์” เหตุผลอยู่ที่ความเร็วในการโหลด (ไฟล์ CSV แบบ gzipped ใช้เวลาโหลดนานกว่า 7 เท่า) และความเข้ากันได้กับขั้นตอนการทำงานของเรา ฉันอาจพิจารณาอีกครั้งว่า R สามารถโหลดไฟล์ Parquet (หรือ Arrow) ได้อย่างง่ายดายโดยไม่ต้องโหลด Spark หรือไม่ ทุกคนในห้องปฏิบัติการของเราใช้ R และหากฉันต้องการแปลงข้อมูลเป็นรูปแบบอื่น ฉันยังคงมีข้อมูลข้อความต้นฉบับอยู่ ดังนั้นฉันจึงสามารถเรียกใช้ไปป์ไลน์ได้อีกครั้ง

การแบ่งงาน

ฉันได้เรียนรู้อะไรบ้าง: อย่าพยายามปรับงานให้เหมาะสมด้วยตนเอง ปล่อยให้คอมพิวเตอร์จัดการเอง

ฉันได้แก้ไขจุดบกพร่องเวิร์กโฟลว์บนโครโมโซมหนึ่งแล้ว ตอนนี้ฉันต้องประมวลผลข้อมูลอื่นๆ ทั้งหมด
ฉันต้องการเพิ่ม EC2 instance หลายๆ ตัวสำหรับการแปลง แต่ในขณะเดียวกัน ฉันก็กลัวว่าจะได้รับโหลดที่ไม่สมดุลอย่างมากในงานประมวลผลต่างๆ (เช่นเดียวกับที่ Spark ได้รับความเดือดร้อนจากพาร์ติชันที่ไม่สมดุล) นอกจากนี้ ฉันไม่สนใจที่จะเพิ่มหนึ่งอินสแตนซ์ต่อโครโมโซม เนื่องจากสำหรับบัญชี AWS มีขีดจำกัดเริ่มต้นที่ 10 อินสแตนซ์

จากนั้นฉันตัดสินใจเขียนสคริปต์ใน R เพื่อปรับงานการประมวลผลให้เหมาะสม

ก่อนอื่น ฉันขอให้ S3 คำนวณว่าแต่ละโครโมโซมมีพื้นที่เก็บข้อมูลเท่าใด

library(aws.s3)
library(tidyverse)

chr_sizes <- get_bucket_df(
  bucket = '...', prefix = '...', max = Inf
) %>% 
  mutate(Size = as.numeric(Size)) %>% 
  filter(Size != 0) %>% 
  mutate(
    # Extract chromosome from the file name 
    chr = str_extract(Key, 'chr.{1,4}.csv') %>%
             str_remove_all('chr|.csv')
  ) %>% 
  group_by(chr) %>% 
  summarise(total_size = sum(Size)/1e+9) # Divide to get value in GB



# A tibble: 27 x 2
   chr   total_size
   <chr>      <dbl>
 1 0           163.
 2 1           967.
 3 10          541.
 4 11          611.
 5 12          542.
 6 13          364.
 7 14          375.
 8 15          372.
 9 16          434.
10 17          443.
# … with 17 more rows

จากนั้นผมเขียนฟังก์ชันที่เอาขนาดรวม สับลำดับโครโมโซม แบ่งออกเป็นกลุ่มๆ num_jobs และบอกคุณว่างานประมวลผลทั้งหมดมีขนาดแตกต่างกันอย่างไร

num_jobs <- 7
# How big would each job be if perfectly split?
job_size <- sum(chr_sizes$total_size)/7

shuffle_job <- function(i){
  chr_sizes %>%
    sample_frac() %>% 
    mutate(
      cum_size = cumsum(total_size),
      job_num = ceiling(cum_size/job_size)
    ) %>% 
    group_by(job_num) %>% 
    summarise(
      job_chrs = paste(chr, collapse = ','),
      total_job_size = sum(total_size)
    ) %>% 
    mutate(sd = sd(total_job_size)) %>% 
    nest(-sd)
}

shuffle_job(1)



# A tibble: 1 x 2
     sd data            
  <dbl> <list>          
1  153. <tibble [7 × 3]>

จากนั้นฉันก็สุ่มเพลงเป็นพันครั้งโดยใช้เสียงฟี้อย่างแมวๆ และเลือกสิ่งที่ดีที่สุด

1:1000 %>% 
  map_df(shuffle_job) %>% 
  filter(sd == min(sd)) %>% 
  pull(data) %>% 
  pluck(1)

ดังนั้นฉันจึงลงเอยด้วยชุดงานที่มีขนาดใกล้เคียงกันมาก จากนั้นสิ่งที่เหลืออยู่ก็คือการรวมสคริปต์ Bash ก่อนหน้าของฉันไว้ในวงวนขนาดใหญ่ for. การเพิ่มประสิทธิภาพนี้ใช้เวลาประมาณ 10 นาทีในการเขียน และนี่ก็น้อยกว่าที่ฉันจะใช้ไปกับการสร้างงานด้วยตนเองหากงานเหล่านั้นไม่สมดุลกันมาก ดังนั้นฉันคิดว่าฉันพูดถูกกับการเพิ่มประสิทธิภาพเบื้องต้นนี้

for DESIRED_CHR in "16" "9" "7" "21" "MT"
do
# Code for processing a single chromosome
fi

ในตอนท้ายฉันเพิ่มคำสั่งปิดระบบ:

sudo shutdown -h now

... และทุกอย่างก็สำเร็จ! เมื่อใช้ AWS CLI ฉันยกอินสแตนซ์โดยใช้ตัวเลือก user_data ให้สคริปต์ Bash ของงานเพื่อการประมวลผลแก่พวกเขา พวกมันทำงานและปิดเครื่องโดยอัตโนมัติ ดังนั้นฉันจึงไม่ต้องจ่ายค่าพลังการประมวลผลเพิ่มเติม

aws ec2 run-instances ...
--tag-specifications "ResourceType=instance,Tags=[{Key=Name,Value=<<job_name>>}]" 
--user-data file://<<job_script_loc>>

แพ็คกันเถอะ!

ฉันได้เรียนรู้อะไรบ้าง: API ควรเป็นแบบเรียบง่ายเพื่อความสะดวกและยืดหยุ่นในการใช้งาน

ในที่สุดฉันก็ได้รับข้อมูลในตำแหน่งและรูปแบบที่ถูกต้อง สิ่งที่เหลืออยู่คือทำให้กระบวนการใช้ข้อมูลง่ายขึ้นมากที่สุดเท่าที่จะเป็นไปได้เพื่อทำให้เพื่อนร่วมงานของฉันง่ายขึ้น ฉันต้องการสร้าง API อย่างง่ายสำหรับการสร้างคำขอ หากในอนาคตฉันตัดสินใจเปลี่ยนจาก .rds กับไฟล์ Parquet นี่น่าจะเป็นปัญหาสำหรับฉัน ไม่ใช่สำหรับเพื่อนร่วมงาน สำหรับสิ่งนี้ฉันตัดสินใจสร้างแพ็คเกจ R ภายใน

สร้างและจัดทำเอกสารแพ็คเกจที่เรียบง่ายซึ่งมีฟังก์ชันการเข้าถึงข้อมูลเพียงไม่กี่ฟังก์ชันที่จัดระเบียบตามฟังก์ชัน get_snp. ฉันยังสร้างเว็บไซต์สำหรับเพื่อนร่วมงานด้วย แพ็คดาวน์เพื่อให้สามารถดูตัวอย่างและเอกสารประกอบได้อย่างง่ายดาย

แยกวิเคราะห์ 25TB โดยใช้ AWK และ R

แคชอัจฉริยะ

ฉันได้เรียนรู้อะไรบ้าง: หากข้อมูลของคุณเตรียมไว้อย่างดี การแคชจะเป็นเรื่องง่าย!

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

# Part of get_snp()
...
  # Test if our current snp data has the desired snp.
  already_have_snp <- desired_snp %in% prev_snp_results$snps_in_bin

  if(!already_have_snp){
    # Grab info on the bin of the desired snp
    snp_results <- get_snp_bin(desired_snp)

    # Download the snp's bin data
    snp_results$bin_data <- aws.s3::s3readRDS(object = snp_results$data_loc)
  } else {
    # The previous snp data contained the right bin so just use it
    snp_results <- prev_snp_results
  }
...

เมื่อสร้างแพ็คเกจ ฉันรันการวัดประสิทธิภาพหลายอย่างเพื่อเปรียบเทียบความเร็วเมื่อใช้วิธีการต่างๆ ฉันขอแนะนำว่าอย่าละเลยสิ่งนี้ เพราะบางครั้งผลลัพธ์ก็ไม่คาดคิด ตัวอย่างเช่น, dplyr::filter เร็วกว่าการจับแถวโดยใช้การกรองตามดัชนีมาก และการดึงคอลัมน์เดียวจากกรอบข้อมูลที่กรองได้เร็วกว่าการใช้ไวยากรณ์การจัดทำดัชนีมาก

โปรดทราบว่าวัตถุ prev_snp_results มีกุญแจอยู่ snps_in_bin. นี่คืออาร์เรย์ของ SNP ที่ไม่ซ้ำกันทั้งหมดในกลุ่ม (bin) ซึ่งช่วยให้คุณสามารถตรวจสอบได้อย่างรวดเร็วว่าคุณมีข้อมูลจากการสืบค้นครั้งก่อนแล้วหรือไม่ นอกจากนี้ยังทำให้ง่ายต่อการวนซ้ำ SNP ทั้งหมดในกลุ่ม (bin) ด้วยโค้ดนี้:

# Get bin-mates
snps_in_bin <- my_snp_results$snps_in_bin

for(current_snp in snps_in_bin){
  my_snp_results <- get_snp(current_snp, my_snp_results)
  # Do something with results 
}

ผลการวิจัย

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

และถึงแม้ว่าแพ็คเกจจะให้รายละเอียดแก่พวกเขา แต่ฉันก็พยายามทำให้รูปแบบข้อมูลง่ายพอที่จะทำให้พวกเขารู้ได้หากพรุ่งนี้ฉันหายไปทันที...

ความเร็วเพิ่มขึ้นอย่างเห็นได้ชัด โดยปกติเราจะสแกนชิ้นส่วนจีโนมที่มีนัยสำคัญเชิงหน้าที่ ก่อนหน้านี้เราไม่สามารถทำเช่นนี้ได้ (กลายเป็นว่ามีราคาแพงเกินไป) แต่ตอนนี้ด้วยโครงสร้างกลุ่ม (bin) และการแคช คำขอสำหรับ SNP หนึ่งรายการใช้เวลาโดยเฉลี่ยน้อยกว่า 0,1 วินาที และการใช้ข้อมูลก็เป็นเช่นนั้น ต่ำที่ต้นทุนสำหรับ S3 นั้นเป็นถั่วลิสง

ข้อสรุป

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

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

สิ่งที่ฉันเรียนรู้:

  • ไม่มีวิธีที่ถูกในการแยกวิเคราะห์ครั้งละ 25 TB
  • ระวังขนาดของไฟล์ Parquet และองค์กรของคุณ
  • พาร์ติชันใน Spark จะต้องมีความสมดุล
  • โดยทั่วไป อย่าพยายามสร้างพาร์ติชัน 2,5 ล้านพาร์ติชัน
  • การเรียงลำดับยังคงทำได้ยาก เช่นเดียวกับการตั้งค่า Spark;
  • บางครั้งข้อมูลพิเศษต้องใช้โซลูชันพิเศษ
  • การรวม Spark ทำได้รวดเร็ว แต่การแบ่งพาร์ติชันยังคงมีราคาแพง
  • อย่านอนตอนที่มันสอนเรื่องพื้นฐานให้กับคุณ เพราะอาจมีคนแก้ไขปัญหาของคุณไปแล้วในช่วงทศวรรษ 1980
  • gnu parallel - นี่เป็นสิ่งมหัศจรรย์ ทุกคนควรใช้มัน
  • Spark ชอบข้อมูลที่ไม่มีการบีบอัดและไม่ชอบการรวมพาร์ติชัน
  • Spark มีค่าใช้จ่ายมากเกินไปเมื่อแก้ไขปัญหาง่ายๆ
  • อาร์เรย์เชื่อมโยงของ AWK มีประสิทธิภาพมาก
  • คุณสามารถติดต่อได้ stdin и stdout จากสคริปต์ R และใช้ในไปป์ไลน์
  • ต้องขอบคุณการใช้งานเส้นทางอัจฉริยะ S3 จึงสามารถประมวลผลไฟล์ได้จำนวนมาก
  • สาเหตุหลักที่ทำให้เสียเวลาคือการปรับวิธีการจัดเก็บข้อมูลให้เหมาะสมก่อนเวลาอันควร
  • อย่าพยายามปรับงานให้เหมาะสมด้วยตนเอง ปล่อยให้คอมพิวเตอร์ทำ
  • API ควรเป็นแบบเรียบง่ายเพื่อความสะดวกและยืดหยุ่นในการใช้งาน
  • หากข้อมูลของคุณได้รับการจัดเตรียมอย่างดี การแคชจะเป็นเรื่องง่าย!

ที่มา: will.com

เพิ่มความคิดเห็น