Clojure и AWS Lambda

Последнее время я неспешно работаю над своим новостным C++ ботом и в качестве заключительного шага его нужно где-то “поселить” жить. Вариантов, конечно же довольно много, начиная своим компьютером заканчивая облаками, но наиболее экономически целесообразным и простым в поддержке выглядит какое-либо бессерверное решение, например AWS Lambda. Так как Clojure приложение как Lambda я еще никогда не запускал, то решил вынести эксперименты отдельно от основного проекта. Оказалось не сложнее чем с Go, всего-то нужно взять подходящую библиотеку для простой и наглядной реализации точки входа, сделать правильную сборку, да не забыть про Terraform для удобного управления инфраструктурой.

Точка входа и правильная сборка

По большому счету это единственные сложности которые возникают при попытке запустить Clojure приложение как Lambda. Вполне ожидаемо, для запуска Java приложения AWS Lambda необходимо знать имя точки входа и эта точка входа должна быть реализована в виде Java класса. По умолчанию Clojure не создает Java классы, но об этом можно попросить при помощи :gen-class. Официальная документация посвященная :gen-class не сказать что легко читается, но на Reddite есть очень доходчивое объяснение зачем это надо. А можно немного упростить себе жизнь и вместо ручного определения Java-классов воспользоваться более наглядным и легко читаемым вариантом – макросом deflambdafn из библиотеки uswitch/lambada. Имя класса заданное в deflambdafn в дальнейшем будет использовано в качестве имени точки входа в AWS Lambda (lambda handler) и понадобится на этапе её создания.

Со сборкой так же есть небольшие тонкости. По умолчанию Clojure компилирует в JVM на лету, что не совсем хорошая идея для AWS Lambda, где время выполнения довольно жестко лимитировано окружением. Так же не маловажно то, что :gen-class генерация отрабатывает только на этапе компиляции и игнорируется в других случаях. Решение предлагаемое Clojure для данной ситуации – компиляция Ahead-of-time. Подобный подход в Clojure дает довольно занятные побочные эффекты, например код ниже не только определит переменную x но и выведет строку в stdout, что в ряде случаев может быть не желательно (пример утащил отсюда).

(def x (do
         (println "Print during compilation?")
         1))

Для активации компиляции Ahead-of-time достаточно добавить флаг :aot :all к свойствам проекта. Еще удобнее ограничить применимость этого флага только uberjar профайлом:

(defproject clj-lambda "0.1.0"
...
  :profiles {:uberjar {:aot :all}})

Причем тут Terraform?

Создавать лямбду можно и через web-консоль, но так как я планирую добавлять к боту новый функционал, то чем меньше действий вручную, тем больше вероятность что я не обленюсь и не брошу дело на полпути. Как оказалось, единственная Clojure-специфичная вещь – это минимальный объем памяти. Доступные по умолчанию 128 Мб ни на что не хватает и лябда падает на старте c OutOfMemoryError, 256 уже хватает, но есть некоторые сомнения по поводу стабильности поведения такого решения, так что разумнее всего остановиться на 512 Мб.

Для того что-бы Terraform мог отслеживать не только изменения в инфраструктуре AWS, но еще и обновление jar-архива с кодом, важно не забыть о поле source_code_hash ресурса aws_lambda_function. Все остальное реализуется более чем стандартно.

2 Comments Clojure и AWS Lambda

  1. Евгений

    Почему clojure а не какая-нибудь другая реализация lisp?

    Reply
    1. Alexander Stavonin

      На мой взгляд только Clojure из всего семейства LISP-ов (оставим elisp в стороне) имеет практический смысл в современном мире. Потому как это LISP с одной стороны, и все библиотеки мира JVM с другой.

      Reply

Leave a Reply to Alexander Stavonin Cancel reply