1

I'm using Filament PHP v3 with Laravel and have a table that can contain 500,000+ user records. When users click the "Select All" button the application crashes with a memory exhausted error:

Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes)
use Filament\Tables\Concerns\InteractsWithTable;

class SendMail extends Page implements HasTable
{
    use InteractsWithTable;

    public function table(Table $table): Table
    {
        return $table
            ->query(User::query()->where('is_admin', 0))
            ->columns([
                TextColumn::make('full_name'),
                TextColumn::make('email'),
            ])
            ->bulkActions([
                BulkAction::make('send_mail')
                    ->form([
                        Wizard::make([
                            Wizard\Step::make('メール入力画面')
                                ->schema(fn($livewire) => [
                                    ViewField::make('user-table')
                                        ->label('')
                                        ->dehydrated(false)
                                        ->view('filament.commons.selected-user-table-wrapper'),
                                    TextInput::make('subject')
                                        ->label('件名')
                                        ->markAsRequired()
                                        ->rules(['required'])
                                        ->validationMessages([
                                            'required' => Config::get('messages.ERROR.MES_001'),
                                        ])
                                        ->columnSpan('full')
                                        ->extraInputAttributes(['maxlength' => "255"]),
                                    Textarea::make('content')
                                        ->label('内容')
                                        ->rows(5)
                                        ->markAsRequired()
                                        ->rules(['required'])
                                        ->validationMessages([
                                            'required' => Config::get('messages.ERROR.MES_001'),
                                        ])
                                        ->columnSpan('full')
                                        ->extraInputAttributes(['maxlength' => "512"]),
                                ]),
                            Wizard\Step::make('メール確認画面')
                                ->schema([
                                    Placeholder::make('mail_subject')
                                        ->label('件名')
                                        ->content(fn(Get $get) => $get('subject')),
                                    Placeholder::make('mail_content')
                                        ->label('内容')
                                        ->content(fn(Get $get) => new HtmlString(nl2br(e($get('content'))))),
                                ]),
                        ])
                            ->previousAction(fn($action) => $action->label('戻る'))
                            ->submitAction(new HtmlString(Blade::render(<<<BLADE
                                <x-filament::button
                                    type="submit"
                                    size="md"
                                >
                                    送信
                                </x-filament::button>
                            BLADE)))
                    ])
                    ->label('メール送信')
                    ->modalSubmitAction(false)
                    ->modalCancelAction(false)
                    ->modalWidth(MaxWidth::FiveExtraLarge)
                    ->action(fn($data, $records) => $this->sendMail($data, $records)),
            ])
    }
}

The problem is that when "Select All" is clicked, $records contains all 500k User models loaded into memory.

How can I either: Limit "Select All" to 150,000 records max and show a warning notification if exceeded

Thanks all

1
  • 1
    Can you share the sendMail function? Also, have you thought of doing background process? As for limiting the records, I believe you can throw an exception in the sendMail function as well. But let's see your code first. Commented Nov 13 at 5:06

1 Answer 1

0
  1. For your warning needs there is the ->count() method
  2. For your limit needs there is the ->take() method

Exemplary Pseudo Code:


$limitInclusive = 150_000;

...

                ->action(function($data, $records) use ($limitInclusive) {

                    assert(is_int($limitInclusive) && (-1 < $limitInclusive));

                    if ($records->count() > $limitInclusive)
                    {
                        Notification::make()
                            ->warning()
                            ->title('Record Limit Exceeded')
                            ->body(
                                sprintf(
                                    'Only the first %s record%s will be processed.',
                                    number_format(limitInclusive),
                                    1 === $limitInclusive ? '' : 's'),
                            ->send();
                    }

                    $this->sendMail($data, $records->take($limitInclusive));
                 }),

Depending on $data or $records is the collection you're interested in to handle this, you have to use the one or the other one. For the example there was just a 50% chance of guessing wrong here, so I mention it explicitly that your code may vary.

Sign up to request clarification or add additional context in comments.

4 Comments

The problem is that I just clicked on "Select all" to select 500k data but it hasn't reached the action yet Maybe when I clicked on it, it pushed 500k data into the selectAllTableRecords state, so the above error was generated
That sounds as if the code as you have it designed is not capable under the memory constraints the system as you have configured it is able to handle the data in the way you require it. How did you calculate the memory limit? Why isn't it aligned with your requirements? Or was this just an experimental exploration that revealed that the system under test has a different limit than expected?
@hakre you are suggesting collection helpers. This will most definitely not solve the problem. using ->take() or ->count() is exactly the problem why code like this gets OOM. If you are in collection context, the query has already completed and many many models might have already been hydrated (put into objects). So you'd rather need query limits or query counts.
@Flame: True, albeit I can't take credit for it, because the OP asked about that. First for the message, which I think will help them to gain the diagnostics they will need to address the problem in their program, and the take() works as a pro-active demonstrator and utility to gain the knowledge about the actual memory requirements. This is a precondition to find the right strategy for the task at hand. That strategy must obviously differ to "collections" alone.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.