개발 꿀팁/PHP

PHP FPM 데이터 캐시 - 실용적인 메모리 테이블

Jammie 2022. 11. 16. 15:00
반응형

간단한 메모리 테이블 MemoryTable
주요 기능:
우리가 어떤 집합내의 데이터를 순회하거나 고주파도로 조회해야 할 때, 매번 데이터 테이블에서 찾는다면, 사이클 횟수가 증가함에 따라 소요시간이 직선적으로 증가할 수 있는데, 이때 우리가 할 수 있는 최적화는 먼저 순회하고자 하는 데이터 집합을 찾아낸 다음, 메모리 중에서 순회하여 필요한 데이터를 선별하는 것인데, 이 메모리 테이블은 바로 이 용도이다.

주로 실현하다
주요 목적:
메모리 테이블을 조작할 때 데이터베이스를 조작하는 것과 같이 연쇄적으로 호출할 수 있고, 간단하고 편리하기를 바란다.
예: table->where->order->limit->select( );
여기서는 field, where, order, limit, find, select에서 흔히 사용하는 방법을 간단하게 구현하였다.

코드 구현

<?php
/**
 */


use think\Exception;

/**
 * Class MemoryTable
 * creator 0505
 * 메모리 테이블은, 주로 테이블의 일부 데이터를 자주 찾을 때의 캐시 처리에 사용된다
 */
class MemoryTable
{
    protected $data=[];//데이터 시트에서 찾아낸 메타데이터
    protected $fields=[];//필드
    protected $whereCallable=null;
    protected $limit=0;
    protected $offset=0;
    protected $orderWay=[];
    protected $orderField=[];

    public function __construct(array $data)
    {
        $this->data=$data;
    }

    public function __destruct()
    {
       $this->clear();
    }

	//오래된 데이터를 정리하다
    private function clear(){
        $this->data=[];
        $this->fields=[];
        $this->whereCallable=null;
        $this->limit=0;
        $this->offset=0;
        $this->orderWay=[];
        $this->orderField=[];
    }

    public function setData(array $data){
        $this->clear();
        $this->data=$data;
        return $this;
    }

    public function getData(){
        return $this->data;
    }
    
    /**
     * 기존의 where 스크리닝 방법을 대체하여 콜백으로 통일하여 간편하고 실용적
     * @param callable $callable,선별 콜백 함수
     * @return $this
     */
    public function whereCall(callable $callable){
        $this->whereCallable=$callable;
        return $this;
    }

    public function limit(int $limit,int $offset=0){
        $this->limit=$limit;
        $this->offset=$offset;
        return $this;
    }

    /**
     * @param string $field ,일시적으로 최대 3개의 필드를 지원하며, 이후 필드는 유효하지 않습니다 "id desc,name asc,age asc"
     * @return $this
     * @throws Exception
     */
    public function order(string $field){
        $tempFields=[$field];
        if (strpos($field,','!==false)){
            $tempFields=explode(',',$field);
        }

        $this->orderField=[];
        $this->orderWay=[];

        foreach ($tempFields as $tempField) {
            if (strpos($tempField,' '===false)){
               throw  new Exception("필드 형식 오류");
            }

            //여러 개의 공백 제거
            while (strpos($tempField,'  '!==false)){
                str_replace('  ',' ',$tempField);
            }

            $tempField=explode(' ',$tempField);
            $this->orderField[]=$tempField[0];
            $this->orderWay[]=$tempField[1];
        }

        return $this;
    }

    public function field(string $field){
        if (!empty($field)){
            $this->fields=explode(',',$field);
        }
        return $this;
    }

    public function find(callable $callable=null){
        $this->limit=1;
        $this->offset=0;
        return $this->select($callable);
    }

    /**
     * @param callable|null $callable,where조건판단 콜백 함수
     * @return array
     */
    public function select(callable $callable=null){
        if (empty($this->data)||!is_array($this->data)){
            return [];
        }
        if (!empty($callable)){
            $this->whereCall($callable);
        }

        if (!empty($this->data)){
            $selectData=[];
            foreach ($this->data as $item) {
               $selectItem=call_user_func($this->whereCallable,$item);
               if (!empty($selectItem)){
                   //필드 필터 여부
                   if (!empty($this->fields)&&!in_array('*',$this->fields)){
                       $newItem=[];
                       foreach ($this->fields as $field) {
                           $newItem[$field]=$selectItem[$field];
                       }
                       $selectItem=$newItem;
                   }
                   $selectData[]=$selectItem;
               }
            }

            //판단순서
            if (!empty($this->orderField)){
               $selectData= $this->arraySort($selectData,$this->orderField,$this->orderWay);
            }

            //절취하다
            if ($this->limit>0){
                $selectData=array_slice($selectData,$this->offset,$this->limit);
            }

            //데이터 비우기
            $this->clear();
            return $selectData;

        }

    }

    //배열 정렬, 일시적으로 최대 3개의 필드 지원
    public function arraySort(array &$data,array $fields,array $way){
        if (empty($data)||empty($fields)||empty($way)||count($fields)!=count($way)){
            return false;
        }

        foreach ($way as &$item) {
            if ($item == 'ASC' || $item == 'asc') {
                $item = SORT_ASC;
            } else {
                $item = SORT_DESC;
            }
        }

        if (count($fields)>=3){
            array_multisort(array_column($data,$fields[0]),$way[0],array_column($data,$fields[1]),$way[1],array_column($data,$fields[2]),$way[2],$data);
        }elseif (count($fields)==2){
            array_multisort(array_column($data,$fields[0]),$way[0],array_column($data,$fields[1]),$way[1],$data);
        }elseif (count($fields)==1){
            array_multisort(array_column($data,$fields[0]),$way[0],$data);
        }
        return $data;
    }

}

사용 & 테스트
테스트 코드 비즈니스 논리:
우리는 어떤 회의 하의 모든 주문서의 구체적인 참가증 및 참가증 인원수, 출석체크 등의 데이터를 집계해야 하기 때문에, 일반적인 조작은 먼저 지정된 회의 하의 모든 주문서를 찾아낸 다음, 주문 번호를 통해 하나하나 집적하는데 필요한 데이터를 찾는 것이다

public function doTest( ){
//미팅을 통해 모든 주문 데이터 찾기
$data=model("MeetOrderQrcode")->where("meet_id"=>9211])->group("order_id")->field("id,order_id")->select(;);
// 총 테스트 데이터 출력량
echo "총 데이터 볼륨 =.count($data)". </br>;

//시작시간 기록
echo "start_time=.microtime(true)". </br>;

//1: 전통적인 표 찾기 방법
foreach ($data as $dataum) {
//선별조건은 회의 id=9211&amp;주문번호=통과된 스파이크번호이며, 상태가 원하는 상태의 모든 참가증 데이터는 id 역순에 따라
$d1= model("MeetOrderQrcode")->where("meet_id"=>9211, "order_id"=>$datum["order_id"], "status"=>["in", "0,1,2,3"]])->order("id desc")->select(;);
// 이게 바로 주문 데이터 찾기
$d2=model('MeetOrder')->where(['meet_id'=>9211, 'order_id'=>$datum['order_id'], 'status'=>['in', '0,1,2,3'])->order('id desc')->select(;);
//뒤에 있는 다른 집합은 쓰지 않습니다...
}

// 메모리 캐시 테이블 사용 시간 시작
echo " finish_time=.microtime(true)". </br>;

// 회의 ID로 메모리 테이블에 필요한 캐시 데이터 찾기
$selectData=model("MeetOrderQrcode")->where(['meet_id'=>9211])->select(;
$selectData2=model("MeetOrder")->where(['meet_id'=>9211])->select(;)

// 메모리 테이블 초기화
$table=new MemoryTable($select Data);
$table2=new MemoryTable($select Data2);

//2: 메모리 테이블 MemoryTale을 통해 데이터를 찾아보니 위의 1의 결과와 일치합니다.
foreach ($data as $dataum) {
//이것은 데이터를 선별하는 콜백으로, 전통적으로 표를 검사할 때의 조건에 따라 변환해야 한다.
// 이 조건은 where(['meet_id'=>9211, 'order_id'=>$datum['order_id'], 'status'=>['in', '0,1,2,3'])와 같습니다.
// 캐시된 데이터는 meet_id=9211의 집합이므로 이 조건은 기본적으로 사용되지 않습니다
$call=function ($item) use ($datum) {
if (in_array($item['status'),[0,1,2,3])&$item['order_id']==$datum['order_id']){
return $item
}
return null;
};

// 메모리 테이블을 호출하여 데이터를 직접 필터링합니다.
$d1=$table->whereCall($call)->order("id desc")->select();

//여기서 두 표의 선별 필드가 정확히 일치하면, 바로 위의 선별 조건을 사용하여 불일치 자신이 새로운 콜백을 만들면 된다.
$d2=$table2->whereCall($call)->order("id desc")->select();
}

//마지막 종료 시간
echo "finish_time_2=.microtime(true);
}

테스트 결과

반응형

'개발 꿀팁 > PHP' 카테고리의 다른 글

Pytho목록  (0) 2022.11.17
PHP에서의 오류 처리 약술  (0) 2022.11.16
PHP RSA 및 RSA2 암호화 코드  (0) 2022.11.09
php 읽기 ini 프로필 속성  (0) 2022.11.09
php 작업 redis 예제  (0) 2022.11.09