I'm working inside a Laravel 9 project where users can purchase "credits" that are then used each time a user uses the API. However, I need to check the user's remaining quota each time, and right now my query is quite slow.
I have a CreditTransaction
model, which stores each transaction in a table with a user_id
and a delta
column which could be positive or negative depending on whether they purchased credits, or credits were used, then I'm performing a sum
of the delta
column on my hasMany
relationship of credit_transactions
, which works, but takes a few seconds to compute when queried.
<?php
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Database\Eloquent\SoftDeletes;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable, TwoFactorAuthenticatable, SoftDeletes;
/**
* The attributes that are mass assignable.
*
* @var array
*/
protected $fillable = [
'role',
'first_name',
'last_name',
'email',
'timezone',
'password',
'origin_source',
'origin_source_other',
'origin_campaign',
'last_login_at',
];
/**
* The attributes that should be hidden for serialization.
*
* @var array<int, string>
*/
protected $hidden = [
'password',
'remember_token',
'two_factor_recovery_codes',
'two_factor_secret',
'origin_campaign',
];
/**
* The attributes that should be cast.
*
* @var array<string, string>
*/
protected $casts = [
'email_verified_at' => 'datetime',
'last_login_at' => 'datetime',
];
/**
* Get the credit transactions for the user
*
* @return int
*/
public function getCreditBalanceAttribute()
{
try {
if (!$this->credit_transactions) {
return 0;
}
$balance = $this->credit_transactions->sum('delta');
if ($balance <= 0) {
return 0;
}
return $balance;
} catch (\Exception $err) { }
return 0;
}
/**
* Get the credit transactions for the user
*
* @return array
*/
public function credit_transactions()
{
return $this->hasMany(CreditTransaction::class);
}
}
I can then perform the following to get my current balance.
Auth::user()->credit_balance
I'm not sure how best to proceed with this, as this is based on a table size of 120k rows which is quite small, equally, I need the credit balance to be accurate and fast, so caching it for 15 minutes isn't an option here.
This is my credit_transactions
table:
Schema::create('credit_transactions', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->index();
$table->foreignId('credit_type_id')->default(1)->index();
$table->foreignUuid('message_id')->nullable()->index();
$table->integer('delta')->index();
$table->timestamps();
$table->softDeletes();
});