I am experiencing a very long TTFB time, around 15000/17000ms with a GET request. This is happening only with one specific call, the rest are fine.
I started experiencing this only after adding Nuxt Auth and Laravel Sanctum. The request remains in pending (under the debugger network tab) for around 10 seconds before completing the request and giving the JSON result.
Here is my nuxt.confing.js
export default {
srcDir: 'resources/nuxt',
ssr: false,
head: {
titleTemplate: '%s - ' + process.env.APP_VERSION,
title: process.env.APP_NAME || '',
meta: [
{ charset: 'utf-8' },
{
name: 'viewport',
content: 'width=device-width, initial-scale=1'
},
{
hid: 'description',
name: 'description',
content: process.env.npm_package_description || ''
}
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
{ rel: 'stylesheet', href: 'https://raw.githack.com/lucperkins/bulma-dashboard/master/dist/bulma-dashboard.css' }
]
},
loading: { color: '#fff' },
css: [
'@/assets/main.scss'
],
plugins: [
"~/plugins/vee-validate.js"
],
components: true,
buildModules: [
'@nuxtjs/dotenv',
'@nuxtjs/eslint-module',
'@nuxtjs/fontawesome',
'@nuxtjs/moment',
],
modules: [
'nuxt-laravel',
// Doc: https://axios.nuxtjs.org/usage
'@nuxtjs/axios',
'nuxt-buefy',
'nuxt-fontawesome',
'@nuxtjs/auth-next'
],
build: {
transpile: [/@fullcalendar.*/,"vee-validate/dist/rules"],
extend(config, ctx) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/,
options: {
fix: true
}
})
}
},
axios: {
baseURL: process.env.API_URL,
debug: true,
credentials: true
},
auth: {
redirect: {
login: '/login',
logout: '/',
callback: '/login',
home: '/dashboard/'
},
strategies: {
'laravelSanctum': {
provider: 'laravel/sanctum',
url: process.env.API_URL
}
},
localStorage: false
},
buefy: {
materialDesignIcons: false,
defaultIconPack: 'fas',
defaultIconComponent: 'font-awesome-icon'
},
router: {
base: '/dashboard/',
linkActiveClass: 'is-active',
middleware: ['auth']
},
fontawesome: {
icons: {
solid: true
}
}
}
Nuxt page (I put only the js code for convenience)
<script>
// https://www.tutsmake.com/laravel-vue-js-full-calendar-example/
import FullCalendar from '@fullcalendar/vue'
import timeGridPlugin from '@fullcalendar/timegrid'
import resourceTimelinePlugin from '@fullcalendar/resource-timeline'
export default {
components: {
FullCalendar
},
data() {
return {
sessions: [],
todayDisabled: true,
calTitle: '',
calendarOptions: {
plugins: [timeGridPlugin, resourceTimelinePlugin],
schedulerLicenseKey: 'CC-Attribution-NonCommercial-NoDerivatives',
initialView: 'timeGridWeek',
refetchResourcesOnNavigate: true,
-->> resources: '/api/sessions', //the very long call
eventDisplay: 'block',
contentHeight: 'auto',
nowIndicator: true,
locale: 'en-gb',
timezone: 'Europe/London', // without this, after Daylight Saving Time the event goes 1 hour back
headerToolbar: false,
businessHours: [
{
daysOfWeek: [1, 2, 3, 4, 5],
startTime: '08:00',
endTime: '20:00'
},
{
daysOfWeek: [6],
startTime: '9:00',
endTime: '14:00'
}
],
slotMinTime: '07:00:00',
slotMaxTime: '24:00:00',
expandRows: true,
eventClick: (calendar) => {
this.$router.push({
name: 'calendar-id-sessiondate',
params: {
id: calendar.event.id,
sessiondate: this.$moment(calendar.event.start).format(
'YYYY-MM-DD'
)
}
})
},
datesSet: (dateInfo) => {
this.calTitle = dateInfo.view.title
this.todayDisabled = this.$moment().isBetween(
dateInfo.start,
dateInfo.end
)
}
}
}
}
}
</script>
Laravel Controller The component "Fullcalendar" runs a GET request through "resources: '/api/sessions'" which goes to the following code.
private function getIntervalTasks($s, $start_period, $end_period)
{
$sessions = [];
foreach(CarbonPeriod::create($s->start_datetime, "$s->interval_num $s->interval_type_human", $s->end_datetime) as $start_session) {
if (Carbon::parse($start_session)->between($start_period, $end_period)) {
$canceled = false;
if ($s->exceptions->isNotEmpty()) {
foreach ($s->exceptions as $e) {
if (Carbon::parse($e->datetime)->toDateString() === $start_session->toDateString()) {
if($e->is_canceled) {
$canceled = true;
break;
} elseif ($e->is_rescheduled) {
$start_session = Carbon::parse($e->datetime);
}
}
}
}
if ($canceled) {
continue;
}
$end_session = Carbon::parse($start_session)->addMinutes($s->duration);
$sessions[] = [
'id' => (int)$s->id,
'title' => $s->client->name,
'start' => $start_session->format('Y-m-d H:i:s'),
'end' => $end_session->format('Y-m-d H:i:s'),
'className' => $s->status_colors
];
}
}
return $sessions;
}
public function index(Request $request) {
$start = (!empty($_GET["start"])) ? ($_GET["start"]) : ('');
$end = (!empty($_GET["end"])) ? ($_GET["end"]) : ('');
$session_period = SessionPattern::has('client')
->where(fn($q)=> $q->whereDate('start_datetime', '<=', $start)->orWhereDate('end_datetime', '>=', $end)
->orWhereBetween(DB::raw('date(`start_datetime`)'), [$start, $end])
->with('exceptions', fn($q) => $q->whereBetween(DB::raw('date(`datetime`)'), [$start, $end])
))->get();
$sessions = [];
foreach ($session_period as $session) {
if($session->is_recurrent){
foreach ($this->getIntervalTasks($session, $start, $end) as $s) {
$sessions[] = $s;
}
} else {
$items = ['none'];
}
}
return response()->json($sessions);
}
ps: I also tried to see if the problem was with Fullcalendar. With a axios call, the issue continues.
I am answering your question based on my similar experience.
But for accurate result i suggest to use php profiling tools like KCachegrind to find out which part of your code consumes more time to execute.
i Think The problem is With Carbon Which was Mine.
Carbon Object Takes Long time to instantiate (is Slow).
i have refactored my code to not use carbon for date compare (make it in DBMS) and everything speeds up.
I Think Your Problem is, you have fetched a lot of records from DB, and loop over them with foreach :
foreach ($this->getIntervalTasks($session, $start, $end) as $s) {
$sessions[] = $s;
}
then in getIntervalTasks
you have a second loop :
foreach(CarbonPeriod::create($s->start_datetime, "$s->interval_num $s->interval_type_human", $s->end_datetime) as $start_session) {
it make the request execution in order of N*M .
Carbon::parse($start_session)->between($start_period, $end_period)
the above code Carbon::parse()
is the slow code which is running in N*M (second degree) times.
I Think Your Solution will be to Avoid Creating Carbon Object In This Order.
Implement Your Business Logic (Time Compare Logic) In DBMS (With Store Procedure Or DB Function) Would Solve The TTFB Issue.
place this
$start = microtime(true);
foreach ($session_period as $session){
...
}
$time_elapsed_secs = microtime(true) - $start;
return response()->json(array_merge($sessions, $time_elapsed_secs);
you can find the time consuming code section using this technique.
and obviously test if my suggestion was correct or not.
NOTE: returning $time_elapsed_secs
is in micro second.