เกี่ยวกับการรั่วไหลของหน่วยความจำ

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

เคล็ดลับ
กระบวนการ monitor ที่มากับ webman จะตรวจสอบการใช้งานหน่วยความจำของกระบวนการทั้งหมด หากการใช้งานหน่วยความจำของกระบวนการใกล้ถึงค่าที่กำหนดใน memory_limit ใน php.ini ระบบจะรีสตาร์ทกระบวนการที่เกี่ยวข้องโดยอัตโนมัติอย่างปลอดภัย เพื่อปล่อยหน่วยความจำ โดยไม่มีผลต่อแอปพลิเคชัน

คำจำกัดความของการรั่วไหลของหน่วยความจำ

การที่การใช้งานหน่วยความจำของ webman เพิ่มขึ้นตามจำนวนคำขอนั้นเป็นเรื่องปกติ โดยทั่วไปเมื่อกระบวนการถึงปริมาณคำขอจำนวนหนึ่ง (โดยทั่วไประดับล้าน) หน่วยความจำจะหยุดเพิ่มหรือเพิ่มเพียงเล็กน้อยเป็นครั้งคราว

ในแอปพลิเคชันส่วนใหญ่ การใช้งานหน่วยความจำต่อกระบวนการจะคงที่ที่ประมาณ 10M–100M ไม่จำเป็นต้องกังวลหากหน่วยความจำต่อกระบวนการไม่เกิน 100M

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

เคล็ดลับ
สำหรับโปรเจกต์ที่แพ็คเป็น phar หรือ binary หากขนาดแพ็คเกจใหญ่ การใช้งานหน่วยความจำเกิน 100M เป็นเรื่องปกติ

วิธียืนยันการรั่วไหลของหน่วยความจำ

หากกระบวนการประมวลผลคำขอเกินล้านครั้ง การใช้งานหน่วยความจำเกิน 100M และหน่วยความจำยังคงเพิ่มหลังแต่ละคำขอ อาจเกิดการรั่วไหลของหน่วยความจำ

วิธีค้นหาการรั่วไหลของหน่วยความจำ

วิธีง่ายๆ คือทดสอบความเครียดแต่ละ API เพื่อหา API ใดที่ยังคงเพิ่มการใช้งานหน่วยความจำหลังจากคำขอหลายล้านครั้ง

เมื่อพบ API ที่มีปัญหาแล้ว ใช้วิธี binary search: แสดงความเห็นครึ่งหนึ่งของโค้ดธุรกิจในแต่ละครั้ง จนกว่าจะระบุส่วนโค้ดที่มีปัญหา

การรั่วไหลของหน่วยความจำเกิดอย่างไร

การรั่วไหลของหน่วยความจำเกิดเมื่อตรงตามเงื่อนไขทั้งสองข้อต่อไปนี้:

  1. มีอาร์เรย์ที่มีวงจรชีวิตยาว (อาร์เรย์ทั่วไปไม่มีปัญหา)
  2. และอาร์เรย์ที่มีวงจรชีวิตยาวนี้ขยายตัวอย่างไม่จำกัด (แอปพลิเคชันใส่ข้อมูลอย่างต่อเนื่องและไม่เคยล้างข้อมูล)

การรั่วไหลเกิดเมื่อตรงตามทั้งสองเงื่อนไขเท่านั้น หากไม่ตรงตามเงื่อนไขใดเงื่อนไขหนึ่ง หรือตรงเพียงข้อเดียว ก็ไม่ถือเป็นการรั่วไหล

อาร์เรย์ที่มีวงจรชีวิตยาว

ใน webman อาร์เรย์ที่มีวงจรชีวิตยาว ได้แก่:

  1. อาร์เรย์ที่มีคำหลัก static
  2. คุณสมบัติอาร์เรย์ของ singleton
  3. อาร์เรย์ที่มีคำหลัก global

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

ต่อไปนี้เป็นตัวอย่างแต่ละกรณี

อาร์เรย์ static ที่ขยายอย่างไม่จำกัด

class Foo
{
    public static $data = [];
    public function index(Request $request)
    {
        self::$data[] = time();
        return response('hello');
    }
}

อาร์เรย์ $data ที่กำหนดด้วยคำหลัก static มีวงจรชีวิตยาว และในตัวอย่าง $data ยังคงขยายตามแต่ละคำขอ ทำให้เกิดการรั่วไหลของหน่วยความจำ

คุณสมบัติอาร์เรย์ singleton ที่ขยายอย่างไม่จำกัด

class Cache
{
    protected static $instance;
    public $data = [];

    public function instance()
    {
        if (!self::$instance) {
            self::$instance = new self;
        }
        return self::$instance;
    }

    public function set($key, $value)
    {
        $this->data[$key] = $value;
    }
}

โค้ดเรียกใช้

class Foo
{
    public function index(Request $request)
    {
        Cache::instance()->set(time(), time());
        return response('hello');
    }
}

Cache::instance() คืนค่า singleton ของ Cache ซึ่งมีวงจรชีวิตยาว แม้ว่าคุณสมบัติ $data จะไม่ได้ใช้คำหลัก static แต่เนื่องจากคลาสเองมีวงจรชีวิตยาว ดังนั้น $data จึงเป็นอาร์เรย์ที่มีวงจรชีวิตยาวเช่นกัน เมื่อเพิ่มข้อมูลที่มีคีย์ต่างกันลงใน $data อย่างต่อเนื่อง การใช้งานหน่วยความจำของโปรแกรมจะเพิ่มขึ้น ทำให้เกิดการรั่วไหล

หมายเหตุ
หากคีย์ที่เพิ่มผ่าน Cache::instance()->set(key, value) จำนวนจำกัด จะไม่เกิดการรั่วไหล เพราะอาร์เรย์ $data ไม่ขยายอย่างไม่จำกัด

อาร์เรย์ global ที่ขยายอย่างไม่จำกัด

class Index
{
    public function index(Request $request)
    {
        global $data;
        $data[] = time();
        return response($foo->sayHello());
    }
}

อาร์เรย์ที่กำหนดด้วยคำหลัก global จะไม่ถูก recycle หลังฟังก์ชันหรือเมธอดคลาสทำงานเสร็จ ดังนั้นจึงมีวงจรชีวิตยาว โค้ดด้านบนจะทำให้เกิดการรั่วไหลเมื่อคำขอเพิ่มขึ้น เช่นเดียวกัน อาร์เรย์ที่กำหนดด้วยคำหลัก static ภายในฟังก์ชันหรือเมธอดก็มีวงจรชีวิตยาว หากอาร์เรย์ขยายอย่างไม่จำกัดก็จะเกิดการรั่วไหล ตัวอย่างเช่น:

class Index
{
    public function index(Request $request)
    {
        static $data = [];
        $data[] = time();
        return response($foo->sayHello());
    }
}

คำแนะนำ

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

หากต้องการหลีกเลี่ยงการรั่วไหลของหน่วยความจำให้มากที่สุด สามารถปฏิบัติตามคำแนะนำต่อไปนี้

  1. พยายามไม่ใช้อาร์เรย์ที่มีคำหลัก global หรือ static หากใช้ต้องให้แน่ใจว่าจะไม่ขยายอย่างไม่จำกัด
  2. สำหรับคลาสที่ไม่คุ้นเคย พยายามไม่ใช้ singleton ใช้คำหลัก new ในการเริ่มต้น หากต้องใช้ singleton ให้ตรวจสอบว่ามีคุณสมบัติอาร์เรย์ที่ขยายอย่างไม่จำกัดหรือไม่