Laravel Live search, sporo?


#1

Sorry, opet ja.
Dakle, napravih live search (jQuery).

Test site (zanemarite izgled, nije bitan, nego samo ova dva polja za search): http://dragutinmitrecic.from.hr/ (i link zanemarite, testna je stranica za igranje, lokalni testni server pored mene u uredu).

Isti upit, Laravel je očajno spor, dok core PHP radi neusporedivo brže za isti search (testirajte npr sa riječi Zadar ili neki drugi Hr grad). Gornji input je sa korištenjem ajax -> Laravel (u produžetku je controller), dok je donji input sa korištenjem core PHP-a (ajax->core PHP file).

Radim li nešto pogrešno ili je normalno? Postoji li neki “trik” da se ne mora učitavati cijeli framework prilikom pretraživanja baze kod ovakvog live searcha? Ili je najbolje (zbog brzine) kod live searcha koristiti core PHP?

Dakle, u ajax (index stranica) stavih:
url:"{{URL::to(’/axsearch’)}}"

U routes (/web.php):
Route::get(’/axsearch’,‘SearchController@search’);

Controller (SearchController.php):

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Gradovi;
use App\Apartman;

class SearchController extends Controller
{
    //
    private $keyword;
    private $foundgrad;
    private $foundapartman;

    public function search(Request $request)
    {
       if($request->ajax()){
            $this->keyword=$request->query('find');
            $this->searchCity();   
            $this->searchApartman();
            $gradovi=$this->foundgrad;
            $apartmani=$this->foundapartman;         
            return view("search")->with("gradovi",$gradovi)->with("apartmani",$apartmani);
       } 
       return redirect()->route("home");
    }

    private function searchCity()
    {
        $grad=Gradovi::select("id","hr")->where("hr","like","%".$this->keyword."%")->take(5)->get();
        $this->foundgrad=$grad;
    }

    private function searchApartman()
    {
        $apartman=Apartman::select("id","naziv")->where("naziv","like","%".$this->keyword."%")->take(5)->get();
        $this->foundapartman=$apartman;
    }
    
}

View (search.blade.php):

<ul class="srch-list">
@foreach($gradovi as $grad)
    <li data-item="{{$grad['id']}}" data-orig="{{$grad['hr']}}"><i class="fas fa-map-marked-alt icon"></i><span>{{$grad['hr']}}</span><p>Grad</p></li>
@endforeach
@foreach($apartmani as $apartman)
    <li data-item="{{$apartman['id']}}" data-orig="{{$apartman['naziv']}}"><i class="fas fa-bed icon"></i><span>{{$apartman['naziv']}}</span><p>Smještaj</p></li>
@endforeach
</ul>

Može li se to kako ubrzati, ili je jedini način da napravim brže pretraživanje u live searchu - koristiti core PHP?


#2

Definitivno je core pretraga (jer se izvršava 1? funkcija) brža od eloquent-a pri kojem se load-uje kompletan sistem (eloquent). Najčešći bottleneck je komunikacija PHP i MySQL servera i tu se odradjuje najbolja optimizacija koda - svesti MySQL upite na prosti minimum.
Mislim da trebaš promijeniti koncept u JS-u.
Problem ti je slanje request-a na keyup event. Network tab in inspector drži request na pending sa svakim novim tipkanjem slova jedne riječi. Tj. kad se natipka “zadar” (koje natipkaš za 1 sekund) 5 request-ova je poslano prema serveru i Laravel 5 puta puni eloquent kroz SearchController::search metod.
Uobičajena stvar je bind-ovati event (keyup) za setTimeout funkciju na recimo 2000 (ms) tako da bi se request poslao 2 sekunde poslije keyup event-a odnosno na enter tipku ili search button klik.
Možeš testirati sa običnom formom bez AJAX-a i bez keyup-a pa ćeš uočiti (relativno zanemarljivu) razliku u brzinama izmedju core-a i laravela.


#3

Da, napraviti ću timeout, nije to problem i znam da tako nije dobro da funkcionira, pitanje je jedino bilo sa ovim pretraživanjem (brzinom odaziva). Zapravo, idem sad to odmah složiti i vidjeti razliku u perfomansama.

Pala mi je u međuvremenu još jedna ideja, ako “preskočim” eloquent i idem direktno na DB::, bi li to ubrzalo rad?

Hvala!


#4

Eloquent je dobar zbog predefinisanih funkcija i posebno, zbog posloženih relacija medju modelima.
Ne bi treb’o biti razlog za izbjegavanje.
DB raw statement-e treba koristiti tamo gdje je query previše komplikovan za eloquent.


#5

Složio timeout, ne kuži se sada razlika u brzini toliko.

Ne znam.

Mislim da ću u ovakim situacijama koristiti ipak core za live search. Napraviti jedan “interface” koji će od frameworka uzimati podatke o spajanju na bazu, uzeti koji je jezik (locale) postavljen od posjetitelja stranica, ali neće includati framework.

Mislim da bi to bila najbolja solucija?


#7

Nisi bas ovaj timeout dobro slozio, i dalje saljes 3 requesta (za rijec “Zadar”) samo sto je svaki odgodjen za 500ms.

Primjer kako bi trebalo:

Ili koristi lodash debounce - https://lodash.com/docs/4.17.5#debounce


#8

Izmjenio sam, gledao si u trenutku dok sam mijenjao (vidjeh i ja da je bio isti broj requesta samo s delayom, na pogrešnom mjestu stavih timeout). :slight_smile:


#9

Jok, ne valja opet, pogledaj malo bolje :slight_smile:


#10

:smiley:
Vani sam na cigareti, pogledat ću, očito fušam :smiley:
Hvala, sad ću ga uneredit pošteno!


#12

Lično ne bi’ to radio.
Zasmetaće u budućoj nadogradnji jer ćeš imati separatisan koncept koji se neće moći lako vezati za ostale funkcije samog sistema (framework-a).
Ja recimo uvijek koristim servis u kontroleru i nikad ne radim direktno sa modelom.
Servis mi je taj koji isporučuje podatke u kontroler.
Kod mene ti je struktura aplikacije sljedeća:

app/City/{Model,Service} # Model i Service su direktorujumi i mogu sadržavati mnoge od vrste

i onda App\City\Service\CityService klasa uključuje model i tamo hvatam podatke iz modela a potom controller koristi servis.

Recimo:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\City\Service\CityService;

class CityController extends Controller
{
    /**
     * @var CityService
     */
    private $cityService;

    public function __construct(CityService $cityService)
    {
        $this->cityService = $cityService;
    }

    /**
     * Display a listing of the resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $cities = $this->cityService->getAll();
    
        return view('city.index', compact('cities'));
    }

    /**
     * Show the form for creating a new resource.
     *
     * @return \Illuminate\Http\Response
     */
    public function create()
    {
        //
    }

    /**
     * Store a newly created resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        //
    }

    /**
     * Display the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function show($id)
    {
        //
    }

    /**
     * Show the form for editing the specified resource.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function edit($id)
    {
        //
    }

    /**
     * Update the specified resource in storage.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function update(Request $request, $id)
    {
        //
    }

    /**
     * Remove the specified resource from storage.
     *
     * @param  int  $id
     * @return \Illuminate\Http\Response
     */
    public function destroy($id)
    {
        //
    }
}

Dalje, samo malo pojednostavljeno ali iz naziva klasa i namespace-ova se može uočiti structura:

<?php

namespace App\City\Service;

use App\App\Service\AbstractService;
use App\Contract\CrudAware;
use App\City\Model\City;

class CityService extends AbstractService implements CrudAware
{
    public function getAll()
    {
        $cities = City::all();

        return $cities;
    }

    // rest of methods I need here (collect from API maybe?)
}

gdje me CrudAware uslovljava sa obaveznim metodama u svakom (koji ga implementira) servisu.
Controller uopšte ne treba da ima vezu sa izvorom podataka jer je to što mu i ime kaže skretničar - ne treba niti ga zanima da li je voz teretni ili putnički. :slight_smile:
Zamisli situaciju da kompanija ponudi affiliate ako im predstavljaš njihove ponude na svom sajtu a koje ti isporučuju preko API-ja. I sada možeš doći do zaključka da ti je isplativije to od potrošnje sopstvenih DB servera i traženja klijenata na dosadašnji način, a možda ti je bolje da tek imaš veću ponudu?
Samo sprovedeš još jedan servis i isporučiš ga takvog kontroleru.
Sa komplikacijom i izlaskom iz okvira framework-a se uvaljuješ u skoro nemoguću misiju održavanja aplikacije ako upotrebljavaš netipična rješenja. Što bi rekla floskula: “zaboravi sve što si dosad radio.” :smiley:
Imaš tu još korisne metodologije poput eloquent resource-a, fractal tj, ready-to-use spatie-eva Laravel implementacija istog.
Druga stvar je kolaboracija. Učiniće se težim nekom drugom i moraće da gleda u pasulj gdje si stavio PHP a gdje Laravel uobičajene metode. Shodno tome, koristeći framework veća šansa je da ti (Laravel) zajednica da rješenje na tekući problem.


#13

Odlična ideja.:smile:


#14

Naravno, u pravu si. :slight_smile:
Zato i postavih pitanje, jer i osobno mi se ne sviđa ideja ubacivanja core u framework aplikaciju. Pa rekoh, možda ima neki trik kojeg ne znam još, a koji bi ubrzao live search.
Ok, neću miješat da ne bi bilo “đaba si krečio”, :smiley:


#15

Otprilike ovako :smiley:

var element = $("#inputsrch"),
    timeout;

element.on("keydown", function(e){
  return e.which !== 13;
})

element.on("keyup", function(e) {

  var keyword= $(this).val();

  if (timeout) {
    clearTimeout(timeout)
  }    

  if(keyword.length > 2){
    timeout = setTimeout(function() {
      // Do stuff
    }, 500);
  } else {
    $("#srch1").remove();
  }

  return e.which !== 13;

})

#16

Pa tako i jest:

var timeout;
var delaytime=500;
    if($("#inputsrch").length>0){
            var element=$("#inputsrch");
            element.on("keydown",function(e){
                return e.which!==13;
            })
            element.on("keyup",function(e){
                var keyword=$("#inputsrch").val();
                if(keyword.length>2){
                    if(timeout) {
                        clearTimeout(timeout);
                    }
                    timeout = setTimeout(function() {
                       // Some stuff
                     },delaytime);

                } else {
                    $("#srch1").remove();
                }
            

            return e.which!==13;
        })

#17

@belmin
Ubaci mu i clear field on escape, šta s’ se stis’o. :grinning:


#18

Moje isprike.

Ili ti nisi bio deploy-ao, ili sam ja gledao u stari tab :smiley:


#19

Nema tu deploya buraz, direktno na njemu radim :smiley:


#20

Samo rokaj :smiley:

BTW imas tu u Laravelu stvar pod nazivom VueJS - jednom kad probas, pozelit ces da se nikad vise ne vratis na jQuery :slight_smile:


#21

Bacih oko čim sam krenuo sa Laravelom. To ide nakon što mi Laravel “legne” u osnovama.
Vidim da je ekipa zaluđena s tim, pa ću morati pobliže to pogledati i svakako usvojiti (kao i sass i laravel mix…)
Backend mi je primaran trenutno, a na jQuery se uvijek mogu oslonit kao na starog druga :slight_smile:


#22

Gledam opet u konzolu.
Sa AJAX-om ti ne treba view već samo JSON rezultati.

return response()->json([
    'gradovi' => $gradovi,
    'apartmani' => $apartmani
]);

A u blade-u/HTML-u/JS-u ih složi kako ti odgovara u listama.