Share ประสบการณ์ Upgrade Python 3.7
![Share ประสบการณ์ Upgrade Python 3.7](/content/images/size/w2000/2022/02/1_oLk4ck1AzJwHxKKJsA0f8g.png)
ถ้าจะมีงานหนึ่งที่ผมทำแล้วรู้สึกสนุกและท้าทายทุกครั้งที่อยู่ที่ Pronto Tools ก็คือการอัพเกรดเวอร์ชั่น Python นี่แหละครับ ตอนผมเข้ามาทำงานใหม่ๆ ทีมใช้ Python 3.5 อยู่ปีที่แล้วเลยอัพเกรดเป็น 3.6 และในปีนี้เนื่องในโอกาส Python 3.7 ออก เราก็รอแปปนึงจนคิดว่าอะไรๆ พร้อมแล้ว ถึงจะอัพเกรด
ทำไมถึงต้องอัพเกรด
เพราะเราอยากใช้ของใหม่ๆ ครับ อันนี้เหตุผลสั้นๆ เลย เราอยากใช้ breakpoint() ตัวใหม่ เราอยากใช้ dataclasses เราอยากใช้ from __future__ import annotations
และอื่นๆ อีกมากมาย นี่ยังไม่รวมถึง Library ต่างๆ ที่เกี่ยวข้องที่บาง Library ทำมาเพื่อ Python 3.7+ แล้ว ถึงแม้ว่า Python 3.6 กว่าจะ end of life ก็ตั้งปี 2021 แต่รีบๆ อัพเกรดไว้ก็ไม่เสียหายครับ
ข้อดีอีกอย่างของการอัพเกรดในช่วงนี้คือ หา Issues ง่ายครับหลายๆ Library ที่เราพึ่งพาอยู่ในโปรเจ็คเราก็ยังมีอีกหลายคนที่ต้องการอัพเกรดเหมือนกัน แต่ถ้าหาไม่เจอ นี่ก็เป็นโอกาสอันดีครับที่จะได้ไป Contribute ให้ Library หลายๆ ตัวให้รองรับ Python 3.7 ในตัว
How to upgrade แบบไม่เจ็บตัว
อย่างแรกเลยครับ ถ้าอยากอุ่นใจ codebase เราควรจะมี Unit test ครับ ซึ่งสิ่งหนึ่งที่ผมเรียนรู้จากช่วงอัพเกรดที่ผ่านมาคือ codebase ของ SimpleSat ที่เราดูแลอยู่บาง service มี coverage อยู่ที่ประมาณ 97% files, 98% lines covered เลยครับ ส่วนตัวผมเองเลยค่อนข้างอุ่นใจไประดับนึงละ
เนื่องจากว่า environment เราอยู่บน Docker 100% โนะ วิธีอัพเกรดคือแค่เปลี่ยน base image จาก Python 3.6 เป็น Python 3.7 แล้วก็ลอง Build images ของทุก microservice service ใหม่ดู จากนั้นก็รัน docker-compose stack ใน local แล้วดูว่าเกิดอะไรขึ้น ข้างล่างนี่คือ List ของปัญหาที่เราเจอกับ Library ที่เราใช้อยู่ครับ
uWSGI
เมื่อประมาณ 2 เดือนที่แล้วเราก็เพิ่งอัพเกรด uWSGI เป็นเวอร์ชั่น 2.0.17 พอมาถึงวันที่เราเปลี่ยนมาใช้ Python 3.7 ระเบิดครับ เหตุผลเพราะว่า C-API PyOS_AfterFork มันดัน Deprecate ใน Python 3.7 ครับ โชคดีที่ Issue นี้มีคนแก้ไว้แล้วและ uWSGI ได้ออก release 2.0.17.1 มาแก้ปัญหานี้แล้วเรียบร้อยครับ
PyToolz
สำหรับคนที่ไม่รู้จักนะครับ PyToolz เป็น Library functional programming ที่ได้รับความนิยมมากตัวหนึ่งใน Python และเราใช้มันในหลายๆ ส่วนของ codebase เราครับ เวอร์ชั่นที่เราใช้อยู่คือ 0.8.2 พออัพเกรดมาใช้ Python 3.7 สิ่งที่เราเจอคือ
SyntaxError: Generator expression must be parenthesized
ซึ่งโค้ดบางส่วนใน Library ยังเขียน Generator expression แบบไม่มีวงเล็บอยู่นั่นเองครับ ซึ่งเป็น Change in behavior หนึ่งใน Python 3.7 นะครับ
Python 3.7 now correctly raises a SyntaxError
, as a generator expression always needs to be directly inside a set of parentheses and cannot have a comma on either side, and the duplication of the parentheses can be omitted only on calls. (Contributed by Serhiy Storchaka in bpo-32012and bpo-32023.)
แต่ก็โชคดีเหมือนกันครับที่ Library ออก patch มาแก้ไขเรื่องนี้แล้วเพียงแค่อัพไป release 0.9.0 ก็ไม่มีปัญหานี้แล้วครับ
Django
เนื่องจากสถาปัตยกรรมของเราเป็น Microservice โนะแล้ว แต่ละ service ก็เกิดก่อนหลังไม่พร้อมกันเลยมีบางตัวที่ใช้ Django ไม่ตรงกับตัวอื่น ปัญหานี้เจอใน Django 1.11.x ครับและเป็นปัญหาเดียวกับ PyToolz เลยคือ Generator expression must be parenthesized
ซึ่งก็มีคนเปิด issue ไว้แล้วทีนี่
แต่ตัวนี้โชคร้ายหน่อยตรงที่ Maintainer ของ Django ไม่อัพเดทให้กับ 1.11 แล้วเนื่องจากหมด Mainstream support ไปแล้วครับ
Per the FAQ, Django 1.11.x is not compatible with Python 3.7.
Django 1.11.x reached end of mainstream support on December 2, 2017 and it receives only data loss and security fixes until its end of life.
เพราะฉะนั้นในเคสนี้ เราเลยเหลือทางเลือกทางเดียวคือ อัพเกรด Django เลยครับจาก 1.11 กระโดดไปเป็น 2.1 ซึ่งเป็นเวอร์ชั่นล่าสุดเลย แต่เนื่องจากเราอัพเกรด Django เพราะฉะนั้น Library ที่เราใช้ที่เกี่ยวข้องกับ Django อย่างเช่น Django REST Framework, django-filter, etc. ก็เลยต้องอัพตามไปด้วย แต่โดยรวมแล้วก็ไม่มีปัญหาอะไรครับ นอกจากต้องเพิ่มอาร์กิวเม้นต์ on_delete เข้าไปใน ForeignKey ของ Django ด้วย
Freezegun
Freezegun ก็เป็น Library ตัวนึงที่เราใช้งานสูงมากครับ ช่วยให้เราเขียน Unit test ที่มีเวลาเข้ามาเกี่ยวข้องได้ง่ายขึ้นมาก เวอร์ชั่นที่เราใช้อยู่ปัจจุบันคือ 0.3.9 ครับ ซึ่งพออัพเกรดมาใช้ Python 3.7 ก็เจอปัญหาเลยว่า
AttributeError: module 'uuid' has no attribute '_uuid_generate_time'
เคสนี้ก็ยังโชคดีอยู่ครับที่ตัว Library ออก patch มาแก้ไขเรียบร้อยแล้วเพียงแค่อัพเกรดเป็น 0.3.10 ก็ไม่เจอปัญหานี้แล้วครับ แต่เคสนี้เป็นเคสที่แปลกมากอย่างนึงคือ ผมพยายามหาใน Changelog ของ Python 3.7 แต่ไม่มีที่ไหนพูดถึง Remove attribute _uuid_generate_time เลยครับ
Dropbox
ในเคสของ Dropbox SDK ปัญหาค่อนข้างจะตรงไปตรงมาครับคือ ใน codebase ของตัว Library ยังมีตัวแปรบางตัวใช้คำว่า async ซึ่งใน Python 3.7 กลายมาเป็น Reserved keywords แล้ว อันนี้ก็อัพเดทไปใช้เวอร์ชั่นล่าสุด 9.0.0 ก็ไม่มีปัญหานี้แล้วครับ
Flake8
พอเราเปลี่ยนมาใช้ Python 3.7 ตัว Flake8 จากที่รันเงียบๆ อยู่ดีๆ ก็มี Warning เพิ่มขึ้นมาว่า
FutureWarning: Possible nested set at position 1
EXTRANEOUS_WHITESPACE_REGEX = re.compile(r'[[({] | []}),;:]')
ซึ่งก็มีคนไปเปิด issues ไว้แล้วครับที่ Pycodestyle (ตัว Flake8 ขี่อยู่บน Library อีกสามตัวหนึ่งในนั้นคือ Pycodestyle ระหว่างนี้เราก็ได้แต่รอ Flake8 ออกเวอร์ชั่นใหม่ ก็ทนรำคาญไปอีกนิดหน่อย แต่โดยรวมแล้วก็ยังทำงานได้ปกติครับ
Celery
ตัว Celery นี่เป็นเคสที่ปวดหัวที่สุดละครับ คือตัว Package หลักที่อยู่ใน PyPI ยังไม่ support Python 3.7 จนมีคนไปโวยวายเยอะมาก ว่าทำไมไม่ support ฟะ ก็น่าจะมีคนใช้เยอะนิ ซึ่งจริงๆ ใน Master branch ของตัว Library เองก็มีคนแก้ปัญหา compatibility กับ Python 3.7 แล้วนะครับ ปัญหาเล็กมากแค่เรื่อง async keywords เหมือนเคส Dropbox เลย แต่ทาง Maintainer ของ Celery ก็ไม่ยอมออก Release เล็กๆ ออกมาแก้เรื่องนี้ (คงจะรอจน release 4.3 ออกอย่างไวสุดก็เดือนหน้า) ครับ
ระหว่างนี้ ถ้าอยากใช้ Celery กับ Python 3.7 ก็แนะนำให้ pip จาก GitHub ที่ commit นี้ไปก่อนนะครับจนกว่า Celery 4.3.x จะออก
pip install git+https://github.com/celery/celery.git@ced86ea58859e9f704cc781c59ea3e137b199638
ส่วนถ้าใครอยากอ่านดราม่าใน Celery ก็ติดตามได้ที่ issues นี้เลยครับ
หลังจากอัพเกรดจนทุก Library ใช้งานได้ปกติแล้ว Unit test ผ่านหมด เราก็ยังไม่ไว้ใจ 100% ครับ วันต่อมาหลังจากเรา Deploy ตัว Docker image ใหม่ที่อัพเกรดแล้วไปเครื่อง development เราทำ Manual testing ทุกฟีเจอร์ที่เกี่ยวข้องกับแอพเลย ซึ่งโชคดีที่ไม่เจอปัญหาอะไรที่ร้ายแรง หรือเกี่ยวข้องโดยตรงจากการอัพเดทครับ เราเลยอัพเกรดขึ้น Production ในบ่ายวันนั้นเลย ซึ่งก็ยังไม่เจอปัญหาอะไรมาจนถึงปัจจุบัน
Upgrade แล้วได้อะไรบ้าง
อันนี้เป็นสิ่งที่ผมนึกได้สุดท้ายก่อนที่เราจะกดอัพเกรดเครื่อง Development ครับคือพยายามเก็บ Resource ทุกอย่างว่าก่อนและหลัง Deploy เปลี่ยนไปมากแค่ไหนและข้างล่างนี่คือ สิ่งที่เกิดขึ้นครับ
![](https://miro.medium.com/max/1400/1*6_5F_P7B4qRK-9OydODnBA.png)
จะเห็นว่า Memory Usage ลดลงอย่างเห็นได้ชัดมากๆ เกือบ 3 GB เลยทีเดียว ถึงแม้วันต่อมามันจะพุ่งมาเฉลี่ยอยู่ที่ ~5GB แต่ก็ไม่ขึ้นไปแตะ 6.75GB เหมือนก่อนอัพเกรดเลยครับ นอกจากนั้นแล้วตอนที่ดู Monitor ใน Datadog สิ่งที่เห็นอีกอย่างนึงคือ Load Average โดยรวมต่อ container ลดลงอย่างเห็นได้ชัดครับ ซึ่งค่อนข้างจะสอดคล้องกับผลที่ได้จาก Benchmark ใน Blog ข้างล่างนี้ครับ
![](https://cdn.hackernoon.com/Devimg/story-image-default.png)
สรุป
การอัพเกรดมีความเสี่ยงนะครับ โปรดใช้ความระมัดระวังในการอัพเกรด แต่ความคุ้มค่าที่ได้มาก็คุ้มที่จะเหนื่อยอยู่ครับทั้งฟีเจอร์ใหม่ๆ ของภาษาและ Library ที่เกี่ยวข้อง
อีกอย่างที่สำคัญและอยากจะฝากไว้คือ Unit Test ครับ เราเขียน Unit Test ไม่ใช่แค่ให้มันผ่านๆ ไป แต่หัวใจของมันคือทำให้เรามั่นใจว่า ตัว Unit ของ Software เราจะยังทำงานเหมือนเดิม ถ้าเราทำการเปลี่ยนแปลงอะไรๆ ใน codebase ครับ
ก็ขอบคุณทุกคนที่อ่านมาจนถึงบรรทัดนี้ครับ หวังว่าจะเป็นประโยชน์แล้วก็ช่วยให้คนที่กำลังลังเลจะใช้ Python 3.7 สบายใจขึ้น แล้วพบกันใหม่ Entry หน้าสวัสดีครับ
Original post at: https://yothinix.medium.com/share-ประสบการณ์-upgrade-python-3-7-3f27300d966d