Tag Archive cache

Byphunsanit

PHP: no-cache css javascript

ตามคำแนะนำในการทำเว็บให้เร็วคือให้แยกไฟล์ภาพ, css และ javascript ออกมาเพราะว่าไฟล์พวกนี้แทบจะเหมือนๆ กันทุกๆหน้า และเปลี่ยนไม่บ่อยนัก การ set server จึงกำหนดให้ทางฝั่ง browser จดจำ static content (css, images,js files) พวกนี้เอาไว้ จะได้ไม่ต้องโหลดไฟล์พวกนี้ใหม่ ทำให้สิ้นเปลือง (สิ้นเปลือง bandwide แบนด์วิธอันล้ำค่าและมีราคาที่เช่ามาจาก isp และ cpu, ram ที่ใช่จัดการจ่ายไฟล์จาก server ไปให้บราวน์เซอร์ของยูเซอร์)

ปัญหาก็คือ เมื่อเราแก้ไฟล์ image, css หรือ javascript อัพโหลดขึ้นไปบน server แล้วแต่ user ยังได้รับไฟล์ version เดิมๆ อยู่ ที่นิยมกันก็คือ ใส่ตัวแปรแบบสุ่มหรือเวลาตามหลังชื่อไฟล์ เช่น

<link href="styles.css?ts=150151" rel="stylesheet" type="text/css">
...
<script src="scripts.js?ts=150151">
...
<img src="logo.png?ts=150151">

เมื่อต้องการให้ใช้ไฟล์ version ใหม่ก็เปลี่ยนค่าของ ts เป็นค่าอื่น ตัว browser จะถือว่าเป็นคนละไฟล์กับไฟล์เดิมที่เก็บไว้ใน cache ของมันเอง ปัญหาก็คือถ้ามีไฟล์พวกนี้ 100 จุด หรือ 1000 จุดก็ต้องไล่แก้ทั้งหมดให้เหมือนๆกันถึงจะเปลี่ยนไฟล์ทุกตัวให้เป็นตัวใหม่พร้อมๆกัน อาจจะทำเป็น global variable หรือใช้ function จัดการให้แต่มันซับซ้อนเกินไป

อีกวิธีที่ทำได้ง่ายแต่ได้ผลกับทุกๆ ไฟล์คือใช้

header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');

เมื่อต้องการให้ browser โหลดไฟล์ใหม่อีกครั้ง โดยใส่ใน header ของเว็บ

อ่านเพิ่มเติม HTTP Caching

Byphunsanit

CodeIgniter: Cache

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

เริ่มจากสร้างไฟล์เก็บ config ขึ้นมาก่อน

<?php
defined('BASEPATH') or exit('No direct script access allowed');

$config['caching']['adapter'] = 'memcached';
$config['caching']['backup'] = 'file';

โดย

adapter
คือ จะเลือกใช้ driver ตัวไหนในการทำ cache ระหว่าง

Drivers Value
Alternative PHP Cache (APC) Caching apc
Dummy Cache dummy
File-based Caching file
Memcached Caching memcached
Redis Caching redis
WinCache Caching wincache
backup
เป็นตัวเลือกสำรองถ้าตัวแรกไม่สามารถทำงานได้ เช่น เครื่องที่เราใช้ไม่ได้ลง Memcached ไว้ ก็จะใช้ file แทน ( cache file ถูกเก็บไว้ที่ \application\cache หรือ ตาม config $config[‘cache_path’])

โหลด Driver ได้ 2 วิธีคือ

Auto-loading
เหมาะกับงานที่ต้องใช้ cache บ่อยๆ ทำได้โดยเพิ่ม ‘cache’ เข้าไป

$autoload['drivers'] = [..., 'cache', ...];
load driver
โหลด driver เฉพาะ controller ที่ต้องใช้ cache เท่านั้น

    public function __construct()
    {
        parent::__construct();

        $this->load->driver('cache');
    }

การใช้งาน

<?php
defined('BASEPATH') or exit('No direct script access allowed');

class Welcome extends CI_Controller
{
    /**
     * Index Page for this controller.
     *
     * Maps to the following URL
     *         http://example.com/index.php/welcome
     *    - or -
     *         http://example.com/index.php/welcome/index
     *    - or -
     * Since this controller is set as the default controller in
     * config/routes.php, it's displayed at http://example.com/
     *
     * So any other public methods not prefixed with an underscore will
     * map to /index.php/welcome/<method_name>
     * @see https://codeigniter.com/user_guide/general/urls.html
     */
    public function index()
    {
        $cacheKey = 'home' . date('Y-m-d'); /* unque id */
        $cacheKey = mb_ereg_replace('([^\w\s\d\-_~,;\[\]\(\).])', '', $cacheKey);

        if (!$datas = $this->cache->get($cacheKey)) {

            $datas = $this->load->view('welcome_message', '', true);

            $this->cache->save($cacheKey, $datas, 300);
        }

        echo $datas;
    }

}

เพราะว่า file driver เป็นการเขียนไฟล์ลงบน hard disk จริงๆ จึงควรแน่ใจว่าไฟล์ถูกรูปแบบที่ os สามารถเขียนไฟล์ได้ โดนอาจจะใช้ md5 เข้ารหัส $cacheKey ก่อน

$cacheKey = md5($cacheKey);

หรือใช้

$cacheKey = mb_ereg_replace('([^\w\s\d\-_~,;\[\]\(\).])', '', $cacheKey);

อ่านเพิ่มเติม

Byphunsanit

เช็ค file owner permission โดย php

อัพงานที่ทำแล้วมีอาการแปลกๆ login ไม่ได้ upload view ขึ้นไปใหม่ก็ไม่เปลี่ยน ลองเข้าไปลบ cache เอง ถึงจะหาย ใช้ไปไม่นานก้เป็นอีก คิดว่าเป็นปัญหาที่ไฟล์ permission และไฟล์ owner ลองสังเกตุดูเจ้าของไฟล์ที่ ftp ขึ้นไปมีเจ้าของคนละเลขกัน (ดูใน filezila ก็ได้มันก็โชว์)

เป็นธรรมดาที่เฟรมเวิร์คเดี๋ยวนี้จะสร้างไฟล์ขึ้นมาเองเพื่อทำเป็น cache หรือ auto-loading ต่างๆ ขึ้นมาเอง อย่าง laravel หลักๆก็จะอยู่ที่ /storage/ ถ้าตัว php ไม่สามารถที่จะเขียนไฟล์ ลบไฟล์ออก หรือแค่ไฟล์ในนี้ไม่ได้จะออกอาการเอ๋อทันที

ใช้ filezilla เปลี่ยน chmod สังเกตุว่าบางไฟล์มันเปลี่ยนสิทธิไม่ได้ มีปัญหา owner แล้วแน่นอน

ก็ขอข้อมูลการติดต่อทาง host จากเจ้าของเห็นชื่อก็สังหรณ์ใจทันที เป็นเจ้าของเดียวก็บโปรแกรม apple ที่มีปัญหาดราม่ากันกับกลุ่มคนทำเว็บเรื่องไม่ update โปรแกรมตัวเอง ติดต่อไปก็งานเข้าทันทีเค้าบอกว่า permission ต่างกันแล้วยังไง มันไม่มีผลอะไร แต่เค้าก็มีปุ่มให้ 2 ปุ่มวิเศษ (ทำไว้ทำไม่วะครับ) คือ

  • กดปุ๊บ จะเปลี่ยน owner ให้ทั้งโพลเดอร์ นานๆ อัพทีคงจะไม่เป็นไร เข้าไปกดมันก็ได้
  • อีกปุ่มคือ “ปล่อยผี” กดแล้วจะเปลี่ยนสิทธิทุกไฟล์เป็น 777 wtf กันเลย ไม่กลัวความปลอดภัยกันเลยใช่มั๋ย งานมันต้องเอาขึ้นแล้วก็ทนๆก่อน

    หลังจากทำทุกอย่างที่เค้าบอกมาแล้ว โปรแกรมเหมือนจะใช้ได้ ยกเว้น upload file ผ่านทาง php ไม่ได้ ดู error log

     at HandleExceptions->handleError('2', 'is_executable(): open_basedir restriction in effect. File(/usr/local/php53/lib/php/) is not within the allowed path(s): (.:/home/www/virtual/xxx/:/usr/local/php53/lib/php/:/tmp/)', '/home/www/virtual/xxx/cms_inside/htdocs/vendor/symfony/process/ExecutableFinder.php', '63', array('name' => 'php', 'default' => false, 'extraDirs' => array('/usr/local/php70/bin'), 'searchPath' => array('.', '/home/www/virtual/xxx/', '/usr/local/php53/lib/php/', '/tmp/'), 'dirs' => array('.', '/home/www/virtual/xxx/'), 'path' => '/usr/local/php53/lib/php/'))

    ไป search หาดูเค้าบอกกันว่า path มันไม่อยู่ใน config open_basedir ทดลองกับเครื่องตัวเองก็เป็นสาเหตุได้จริงๆ ก็ส่งอีเมล์ ไปขอให้แก้ config ให้ใหม่ใส่ /usr/local/php70/bin เพิ่มเข้าไป คุณท่านก็บอกกลับมาว่า มันไม่เกี่ยว เค้าก็เขียน php เหมือนกัน แต่ก็ยอมแก้ให้ หลังจากนั้นไฟล์ก็ี upload ได้ปกติ ชักจะเริ่มสงสัยแล้วว่า config server แล้วก็เขียนโปรแกรมเป็นจริงๆ รึเปล่า

    ต่อมา เจออีกกว่าส่งอีเมล์ออกไม่ได้ เพราะว่าไป disable บางอย่างไว้ ครั้งนี้เปลี่ยน host ละไม่ไหวเจ้านี้ เลยเขียน script ไว้ตรวจเบื้องต้น รอเค้าเซ็ตไปก่อน (เหมือน server ในไทยนี่ยังใช้อัตโนมือกันอยู่เลย ทำให้ต้องรอ 1 วันเต็มๆ)

    <?php
    echo '<br>Current script owner: ' . get_current_user();
    echo '<hr>';
    
    echo '<br>file upload by ftp owner = ' . fileowner('test.php');
    echo '<br>file upload by ftp group = <pre>' . print_r(posix_getgrgid(filegroup('test.php')), true), '</pre>';
    echo '<hr>';
    
    echo '<br>file index.php owner = ' . fileowner('index.php');
    echo '<br>file index.php group = <pre>' . print_r(posix_getgrgid(filegroup('index.php')), true), '</pre>';
    echo '<hr>';
    
    file_put_contents('testFile.php', '<?php phpinfo();');
    echo '<br>create by php owner = ' . fileowner('testFile.php');
    echo '<br>file upload by php group = <pre>' . print_r(posix_getgrgid(filegroup('testFile.php')), true), '</pre>';
    unlink('testFile.php');
    

    ถ้าทั้งหมดได้ user id เดียวกัน หรืออยู่ group เดียวกันก็เบาใจ แต่ไม่จำเป็นว่า group จะต้องเป็น www-data เคยเจอว่าบางที่เค้าจะสร้าง group ใหม่ให้ลูกค้าแต่ละเจ้าไปเลยเพื่อความปลอดภัย

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

Byphunsanit

ใช้ cache ใน YII 2

เวลาทำเว็บยุคปัจจุบันที่ปรับข้อมูลสำหรับ user แต่ละคน แต่ความจริงๆ แล้วแต่ละคนไม่ได้รับข้อมูลที่ต่างกันทั้งหมด บางส่วนก็เหมือนกับคนอื่นๆ ตามเงื่อนไข แทนที่จะ render ใหม่ทุกครั้ง ในระบบขนาดใหญ่ก็นิยมแยกเป็นส่วนๆแล้วใช้ระบบแคช จำส่วนนั้นๆ แทนที่การคำนวณใหม่ สำหรับทุกคน สำหรับทุกครั้งที่มีการเรียกหน้าเว็บ

ใน YII 2 มี cache ให้เลือกทำ Data Caching หลายตัว เช่น APC, file, memcache, memcached, WinCache, XCache, YII2, Zend แต่หลักการเหมือนๆ กันคือ

  1. สร้าง Cache Keys ขึ้นมาให้แยกต่างจากตัวอื่นๆ ในกลุ่ม นิยมใช้ ตัวแปรที่ส่งเข้าฟังก์ชั่นมาเป็น unique ให้ต่างจากตัวอื่นๆ
  2. เช็กดูว่า Cache Keys ตัวนี้มีอยู่รึเปล่า ถ้ามีก็ให้เอา cache มาใช้โดยไม่คำนวณใหม่ ถ้าไม่มีก็สร้างขึ้นใหม่
  3. เพราะว่าข้อมูลบางส่วนอาจจะเก่า หรือใช้ไม่ได้ เช่น มีการเพิ่มข้อมูลเข้ามาใหม่ หรือมีการแสดงวันที่อยู่ข้างใน ระบบแคช จึงมีทางเลือกให้ลบออกตามระยะเวลา expired / invalidated ตามช่วงเวลา yii ใช้หน่วยเวลาเป็น วินาที ตามตัวอย่างใช้ 86400 คือ 1 วัน แสดงว่าทุกๆ วันไฟล์นี้จะโดนลบออกไป
	public function actionIndex($id)
	{

		/* ใช้ cache ตัวไหนเลือกจาก http://www.yiiframework.com/doc-2.0/guide-caching-data.html Supported Cache Storage */

		$cache = Yii::$app->cache;

		/* สร้าง Cache Keys ชื่อ saveName ต่อด้วยตัวแปรที่ส่งเข้ามา มีหลายตัวก็ต่อไปเรื่อยๆ */

		$cacheKeys = 'saveName'.$id;

		/* ดูว่ามีแคชชื่อนี้รึยัง */

		$data = $cache->get($cacheKeys);

		/* ถ้าไม่มีก็สร้างใหม่ใส่ตัวแปร data  */

		if ($data === false) {

		/*
		ถ้าไม่ต้องการ layout ใช้
			$data = $this->renderPartial('index', [
		*/
			$data = $this->render('index', [
				'id' => $id,
			]);

		/* เก็บข้อมูลเข้า cache เวลาถ้าไม่ใส่จะไม่ลบออกตามเวลา */

			$cache->set($cacheKeys, $data, 86400);

		}

		/* ส่ง output ออกไปทั้งจาก cache หรือจากการ render ใหม่ */
		return $data;
	}

ทดสอบดูโดย

  • cacheKey ถ้่าหากตรวจพบว่าไม่ใช้ตัวอักษร alphanumeric โดย function alphanumeric() หรือ ยาวกว่า 32 ตัวอักษร จะถูก hash เป็น string ยาว 32 ตัวอักษร โดยใช้ md5()
  • เมื่อเปิด folder cache เช่น \backend\runtime\cache จะเห็นว่าไฟล์จะถูกสร้างมาใหม่ตามรูปแบบ อักษร 2 ตัวแรกของ cacheKey\cacheKey.bin เปิดดูข้างในจะเป็นข้อมูลที่ถูก เปลี่ยนโดย function serialize() เก็บเอาไว้
  • เปลี่ยนตัวแปรที่ส่งเข้า function ดูว่าถ้าตัวแปรเหมือนเดิมมันจะไม่สร้างไฟล์ใหม่ เว็บเราก็แสดงผลเร็วกว่า ถ้าตัวแปรไม่เหมือนกันก็จะมีไฟล์ถูกสร้างมาใหม่
Byphunsanit

Basic image gallery

แกลเลอร์รี่รูปภาพง่ายๆโดยใช้เจคิวรี่ โดยกดเปลี่ยนภาพแล้วมันจะ swap ภาพไปอีกภาพโดยมีเฟตอิน แฟตเอ้า สำหรับแสดงสินค้าแบบง่ายๆ มีการแคชภาพไว้ล่วงหน้า เวลาเปลี่ยนภาพจะได้รวดเร็ว

ตกแต่งอัลบัมของเราซักเล็กน้อยก่อน

#imgPreview {
	height: 481px;
	width: 431px;
}
.thumbs li {
	float: left;
	height: 60px;
	margin: 18px 9px;
	width: 74px;
}
.thumbs li img.active {
	border:#c00000 sold thin;
}
  1. ใส่ภาพโดยภาพในทัมเนลจะตั้งชื่อให้เหมือนกับภาพใหญ่ เก็บไว้ที่เดียวกัน ต่างกันแค่ ภาพตัวอย่างจะต่อด้วย -thumb เพื่อความสะดวกในการดูแล
  2. class=”active” จะตรงกับภาพปัจจุบันเที่แสดงในไอดี imgPreview จะไว้เขียนตกแต่งเพิ่มเติมให้ผู้ใช้ทราบว่ากำลัง show ภาพตัวไหนอยู่
  3. ถ้าเปลี่ยนภาพแล้วกระตุกเพราะหน้าเพจขยับให้ใส่ วัตถุที่มีความยาว หรือความกว้างเท่ากับภาพในแนวที่เปลี่ยนไป จะได้ถีบไว้เวลาเปลี่ยนภาพ เช่น
    <hr width="431">
<img id="imgPreview" src="assets/products/1-big.jpg">
<ul class="thumbs">
	<li><img class="active" src="assets/products/1.jpg"></li>
	<li><img src="assets/products/2-thumb.jpg"></li>
	<li><img src="assets/products/3-thumb.jpg"></li>
	<li><img src="assets/products/4-thumb.jpg"></li>
	<li><img src="assets/products/5-thumb.jpg"></li>
	<li><img src="assets/products/6-thumb.jpg"></li>
	<li><img src="assets/products/7-thumb.jpg"></li>
</ul>

effect จะตั้งไว้ที่ 300 มิลลิวินาที ถ้าต้องการให้แสดงเร็วช้ากว่านี้ทดลองเปลี่ยนดูครับ

$(function()
{
	/* preload images cache */
	var images = new Array()
	$('.thumbs li img').each(function(index, value)
	{
		console.log(index+' '+$(this).attr('src'));
		src = $(this).attr('src').split('.jpg');
		src = src[0]+'-big.jpg';
		images[index] = new Image()
		images[index].src = src;
	});

	$('.thumbs li img').click(function()
	{

		thumb = $(this);
		if(thumb.hasClass('active'))
		{
			return true;
		}

		$('.thumbs li img').removeClass('active');
		thumb.addClass('active');

								/* บรรทัดนี้ทำให้เปลี่ยนรูปไปใช้อีกไฟล์ */
		src = thumb.attr('src').replace('-thumb.', '.');

		$('#imgPreview').fadeOut(300, function(){
			$(this).attr('src', src).bind('onreadystatechange load', function()
			{
				if(this.complete)
				{
					$(this).fadeIn(300);
				}
			});
		});

	});
});
Byphunsanit

ปรีโหลดรูปภาพ

เวลาทำงานกับรูปภาพ ปัญหาที่เจอบ่อยก็คือ รูปโหลดไม่ทันเห็นเป็นหน้า เว้าๆ แหว่งๆ วิธีแก้คือการทำ preload เอารูปมาเก็บเอาไว้ใน แคช (cache) ก่อนเวลาใช้จะได้มีทันใช้

function loadImages(sources)
{
	images = {};
	var loadedImages = 0;
	var numImages = 0;
	/* get num of sources */
	for(var src in sources)
	{
		numImages++;
	}
	for(var src in sources)
	{
		images[src] = new Image();
		images[src].onload = function()
		{
			if(++loadedImages >= numImages)
			{
				/* ทำงานได้แล้ว  ใส่ function ต่อไปตรงนี้ */
				return images;
			}
		};
		images[src].src = sources[src];
	}
}

var sources = {
	darthVader: 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg',
	yoda: 'http://www.html5canvastutorials.com/demos/assets/yoda.jpg',
};

images = loadImages(sources);
Byphunsanit

แก้ YII Assets ย้าย static cache folder

โดยปกติ yii จะเก็บไฟล์ static บางไฟล์ที่เป็น CSS, JavaScript ทั้งที่เป็น framework อย่าง jQuery หรือไฟล์ที่เกิดจากการใช้คำสั่ง Yii::app()->getClientScript()->registerScript(‘featured’ ,”…”); หรือ Yii::app()->clientScript->registerCoreScript(‘jquery’); ไว้ใน folder assets/ค่าสุ่ม บางครั้งเราแก้ไขไปแล้ว แต่ไฟล์พวกนี้ยัง cache ของเดิมอยู่เพราะว่า hash ไม่ได้ต่างจากเดิม หน้าเว็บจึงทำงานไม่ถูกต้องตามที่ตั้งใจไว้ วิธีแก้คือการที่จะต้องเข้าไปลบออกทั้งโฟลเดอร์ ซึ่งไม่สดวกนักเพราะว่าใน assets ยังมีไฟล์ที่เป็นดาต้าจริงๆ อยู่ด้วย ทางที่จึงควรแยกไฟล์ที่เป็น static cache พวกนี้ออกไปไว้ที่อื่นเพื่อความสดวกในการทดสอบและปลอดภัยของข้อมูล

สมมุติว่าสร้างโฟลเดอร์เก็บไฟล์ static cache ไว้ที่ D:/xampp/htdocs/yiiTest/assetsRuntime (user ต้องเข้าถึงได้จาก internet และต้องกำหนดให้ php มีสิทธิเขียน / ลบไฟล์ อย่างน้อย cmod ต้องเป็น 0755 ) แล้วไปที่ protectedconfig main.php เพิ่มบรรทัดตามตัวอย่าง

	'basePath'=>dirname(__FILE__).DIRECTORY_SEPARATOR.'..',
	'name'=>'My Web Application',
…
	// application components
	'components'=>array(
		'assetManager'=>array(
			'basePath'=>'D:/xampp/htdoc/syiiTest/assets/Runtime',
			'baseUrl'=>'/yiiTest/assetsRuntime/'
		),
…

อาจจะลบโฟลเดอร์ static cache ใน assets เก่าออก ครั้งต่อไปที่เขียนไฟล์ จะเขียนลงที่ใหม่