Назад | Перейти на главную страницу

Очень медленный вызов imagemagick convert при использовании AWS Lambda

У меня есть относительно простое составное действие, которое я выполняю с помощью imagemagick на AWS Lambda. Почему на выполнение часто уходит больше минуты?

Это объединяет четыре изображения меньшего размера «позади» большего фона с прозрачными отверстиями:

convert -size 1280x720 'xc:#747784'  \
        \( /tmp/$uuid/face-1.png -background none -distort SRT "0.132 16.094" \) -geometry +324.929+110.781 -composite \
        \( /tmp/$uuid/face-2.png -background none -distort SRT "0.132 24.486" \) -geometry +401.5+106.375 -composite 
        \( /tmp/$uuid/face-3.png -background none -distort SRT "1 0" \) -geometry +-166+-250  -composite 
        \( /tmp/$uuid/face-4.png -background none -distort SRT "1 0" \) -geometry +-166+-250  -composite 
        /tmp/$uuid/____MASTER_720P_00230.png -composite \
        -depth 8 png:/tmp/$uuid/230.png

На моем (6-летнем) ноутбуке эти операции обычно занимают от 2 до 3 секунд.

В Lambda (даже с учетом накладных расходов на копирование пяти исходных файлов из s3 в файловую систему / tmp) это занимало около 30 секунд и часто достигало максимального времени выполнения, которое я установил (1 минута). Я увеличил процессор и таймаут до 2 минут 30 секунд, но я все еще видел таймауты.

Встроенный в Lambda ImageMagick (v6) предположительно имеет ошибку с поддержкой OpenMP, поэтому я попытался смягчить ее, используя:

OMP_NUM_THREADS=1 \
MAGICK_THREAD_LIMIT=1 \
convert -size 1280x720 'xc:#747784'  \
        \( /tmp/$uuid/face-1.png -background none -distort SRT "0.132 16.094" \) -geometry +324.929+110.781 -composite \
        \( /tmp/$uuid/face-2.png -background none -distort SRT "0.132 24.486" \) -geometry +401.5+106.375 -composite 
        \( /tmp/$uuid/face-3.png -background none -distort SRT "1 0" \) -geometry +-166+-250  -composite 
        \( /tmp/$uuid/face-4.png -background none -distort SRT "1 0" \) -geometry +-166+-250  -composite 
        /tmp/$uuid/____MASTER_720P_00230.png -composite \
        -depth 8 png:/tmp/$uuid/230.png

Но это все еще давало мне время работы от двух минут и выше (часто доходило до тайм-аута).

Затем я скомпилировал полностью статический ImageMagick v7 (7.0.7-13) (добавив --with-quantum-depth=8 чтобы попытаться уменьшить накладные расходы на обработку) и добавил это в мой пакет Lambda и изменил вызов convert на это:

/var/task/bin/magick -size 1280x720 'xc:#747784'  
                     \( /tmp/$uuid/face-1.png -background none -distort SRT "0.132 16.094" \) -geometry +324.929+110.781  -composite \
                     \( /tmp/$uuid/face-2.png -background none -distort SRT "0.132 24.486" \) -geometry +401.5+106.375  -composite \
                     \( /tmp/$uuid/face-3.png -background none -distort SRT "1 0" \) -geometry +-166+-250  -composite \
                     \( /tmp/$uuid/face-4.png -background none -distort SRT "1 0" \) -geometry +-166+-250  -composite \
                     /tmp/$uuid/____MASTER_720P_00230.png -composite \
                     -depth 8 png:/tmp/$uuid/230.png

Но по-прежнему нет заметного улучшения во времени выполнения.

Вот код для всего вызова Lambda (он немного отредактирован вручную для публикации здесь, поэтому любые ошибки исключительно в этом фрагменте)

var Q = require('q');
var path = require('path');

// A lovely set of composable modules
// https://github.com/lambduh/lambduh
var execute = require('lambduh-execute');
var s3Download = require('lambduh-get-s3-object');
var upload = require('lambduh-put-s3-object');
var s3Upload = require('lambduh-put-s3-object');

// This contains the relevant imagemagick convert call for each frame, indexed
// by frame
var animationFrames = require('./composite.json');

var bucket = "super.secret.bucket";

var frame, paddedFrame, uuid;

process.env['PATH'] = process.env['PATH'] + ':/tmp/:' + process.env['LAMBDA_TASK_ROOT']

exports.handler = function(event, context) {

    frame = event.frame;
    paddedFrame = ("00000" + frame).slice(-5);
    uuid = event.uuid;

        console.log("bucket: ", bucket);
        console.log("frame: ", frame);
        console.log("folder: ", uuid);

      // make the /tmp/uuid directory where we'll download the face pngs and
      // the relevant frame png to. Tidy up from old runs first.
      execute(event, {
          shell: "rm -rf /tmp/*; mkdir -p /tmp/" + uuid,
          logOutput: true
      })

      //now get the face pngs and frame png and put them there
      .then(function(event){

        var def = Q.defer();
        var s3Files = []
        var message = {};

        s3Files.push("backgrounds/____MASTER_720P_" + paddedFrame + ".png");
        s3Files.push("videos/" + uuid + "/face-1.png")
        s3Files.push("videos/" + uuid + "/face-2.png")
        s3Files.push("videos/" + uuid + "/face-3.png")
        s3Files.push("videos/" + uuid + "/face-4.png")


        var promises = [];

        s3Files.forEach(function(s3File) {
          console.log("Going to download %s/%s to /tmp/%s/%s", bucket, s3File, uuid, path.basename(s3File));
          promises.push(s3Download(event, {
            srcBucket: bucket,
            srcKey: s3File,
            downloadFilepath: "/tmp/" + uuid + "/" + path.basename(s3File)
          })
          .fail(function(){
            console.log("Couldn't download ", s3File);
          })
        )});

        Q.all(promises)
          .then(function(event) {
            def.resolve(event[0]);
          })
          .fail(function(err) {
            def.reject(err);
          });
        return def.promise;
      })

      .then(function(event){
        console.log("Compositing with imagemagick");
        return execute(event, {
          shell: "export uuid=" + uuid + " && " + animationFrames[frame],
          logOutput: true
        })
      })

      .then(function(event){
        console.log("Going to upload /tmp/%s/%s.png to %s/videos/%s/%s.png", uuid, frame, bucket, uuid, frame);
        return s3Upload(null, {
          dstBucket: bucket,
          dstKey: "videos/" + uuid + "/" + frame + ".png",
          uploadFilepath: "/tmp/" + uuid + "/" + frame + ".png"
        })
      })

      .then(function(event){
        console.log("finished");
        console.log(event);
        context.succeed(frame)
      })

      .fail(function(err) {
        console.log(err);
        //fail soft so lambda doesn't try to run this function again
        context.done(null, err);
      });
}