nklvjvc's avatar

Uploaded files randomly get same position number

Hello, I am using filepond to upload images combined with intervention/image. For sorting images I need every uploaded image to have position incremented for 1 (position is column in database). So I have them starting from 0 and they keep incrementing. Problem is sometimes they get same position number, for example 0 1 2 3 3 4 5 6 and if I upload 60 images at once it will happen few times, i might have double 11, double 34 etc, and every time it happens at different numbers, even 0 0 1 2 3 was once. I can see in db that if I have 60 records my max position is 55 or 52, its different number every time. I hope I managed to explain the problem clearly, appreciate any idea or solution how to solve or troubleshoot this.

public function filepondUpload(Property $property)
    {
        if (request()->hasFile('image')) {
            
            // get max position
            $max = $property->images()->max('position') ?? -1;

            // file name without extension
            $file_name_without_extension = uniqid('', true) . time();

            // validate input files
            $validated = request()->validate([
                'image' => ['image', 'max:5120'],
            ]);

            $file = $validated['image'];

            // read uploaded image
            // convert to webp with compressed quality of 90%
            // save image and add .webp extension to file name
            $manager = new ImageManager(new Driver());
            $image = $manager->read($file);
            $watermark = false;
            if ($watermark) {
                $image->place('img/logo100.png', 'bottom-left', 40, 40, 90);
            }
            $image = $image->encodeByMediaType('image/webp', quality: 90);
            Storage::disk('public')->put('properties/' . $property->id . '/' . $file_name_without_extension . '.webp', (string) $image);

            // read uploaded image
            // transform image to 300x300
            // convert to webp with compressed quality of 90%
            // save image and add .webp extension to file name
            $manager = new ImageManager(new Driver());
            $thumbnail = $manager->read($file);
            $thumbnail = $thumbnail->cover(300, 300);
            $thumbnail = $thumbnail->encodeByMediaType('image/webp', quality: 90);
            Storage::disk('public')->put('properties/' . $property->id . '/thumbnails/' . $file_name_without_extension . '.webp', (string) $thumbnail);
  
            // Save to db
            Image::create([
                'property_id' => $property->id,
                'name' => $file_name_without_extension . '.webp',
                'position' => $max + 1,
                'public' => 1,
            ]);

            // filepond response
            return $file_name_without_extension;
        }
    }
0 likes
6 replies
Tray2's avatar

That sounds like a really bad solution, why not just create a table that contains the image name and the batch, then use the incrementing id as the counter.

nklvjvc's avatar

@Tray2 i need position column for sorting as I am using sorting method from Caleb Porzio using livewire and i need them starting from 0 and if they have matching numbers like it’s happening in my case sorting doesn’t work properly.

I just don’t understand how can it give same number when every time it pulls max number and increments it

Tray2's avatar

@nklvjvc I would still do it the way I told you, and then if you need numbers from 0 to something you fetch that from the database.

martinbean's avatar

I just don’t understand how can it give same number when every time it pulls max number and increments it

@nklvjvc Because you have a race condition. Between fetching the maximum number:

$max = $property->images()->max('position') ?? -1;

And actually using that number:

Image::create([
    'property_id' => $property->id,
    'name' => $file_name_without_extension . '.webp',
    'position' => $max + 1,
    'public' => 1,
]);

You do a ton of work such as (needlessly) generating your own filename, validation, reading the file into memory, adding a watermark, encoding it to WebP, storing it publicly (again, needlessly), reading the file again, creating a 300×300 thumbnail, encoding that to WebP, storing that file, and only then do you create the database record and increment the position value you selected way above.

Well, in that time, more than one image may have been uploaded and then when your filepondUpload method is called, multiple images have started, called $property->images()->max('position') ?? -1 and gotten the same result, meaning they’ll get the same position value when they do $max + 1.

You need to lock your table between reading the maximum position value and then incrementing that value when you save the record. To avoid locking your database for prolonged periods of time (because reading and writing and converting images aren’t small tasks) I would just upload the image as is, and then have a queued job that converts the image and creates any thumbnails.

nklvjvc's avatar

@martinbean I finally managed to do it, it was such a long day. Your comment was so helpful. At first, I tried to slim the controller as much as possible but the more light it was, it got more race conditions. In the end, I ended up setting 'public' column to have a default value of 1 in migrations and started using locks, db transactions, and filepond for image transformations. So everything looks so much lighter now, except still using intervention/image for making thumbnails. Thank you!

1 like
Merklin's avatar

You can have a database field like $table->unsignedInteger('order_column'); that will keep the number of the file and then use spatie/eloquent-sortable package by adding SortableTrait to the model.

Please or to participate in this conversation.