Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Microservices in Clojure

Microservices in Clojure

Lessons learned after 1.5+ years of developing/evolving microservices at Attendify. Ideas, problems, pitfalls, plans.

Oleksii Kachaiev

April 17, 2015
Tweet

More Decks by Oleksii Kachaiev

Other Decks in Programming

Transcript

  1. About Me ‣ Alexey Kachayev, @kachayev ‣ CTO at Attendify.com

    ‣ Clojure, Scala, Erlang engineer ‣ Active open source contributor ‣ Author of Fn.py library (Python) ‣ Hobbies: Haskell, Rust, CRDTs, compilers
  2. Agenda ‣ Product overview ‣ The big idea behind Microservices

    ‣ What we built and why ‣ Problems and pitfalls ‣ The Road Not Taken
  3. Attendify ‣ Mobile applications builder ‣ Thousands of mobile apps

    ‣ Private social networks in each application ‣ Real-time analytic ‣ Sponsored Posts (ads) ‣ EventWall for screen projection
  4. Microservices ‣ Your Server As a Function [1] ‣ Scaling,

    multiple languages and bla-bla-bla… ‣ Hyped as well as NoSQL, BigData etc ‣ Just google it to find more information ‣ We use it because it’s convenient ‣ As well as split your code into small functions ‣ We moved from Django project almost 2 years ago
  5. Applicability ‣ If you don’t know how to split your

    system into small services: -it’s too small to be split -you don’t know your system well enough -how are you going to scale your engineering team?
  6. Current State ‣ 7 services in Clojure (from a total

    of 23) ‣ 82 RPC endpoints in Clojure (from a total of 290+) ‣ 17k+ LOC of Clojure code, 2850+ commits ‣ 4-6M requests handled each day ‣ 3 engineers work with Clojure on a regular basis ‣ Not only Clojure company (also Erlang, Scala, Go)
  7. Brief History ‣ Started 1.5 year ago ‣ With 2

    services in Clojure (sophisticated data processing modules) ‣ Didn’t choose any of existing microservices framework or platform
  8. Ready-to-use Solutions ‣ All systems are targeted to fit in

    predefined requirements (as any framework) ‣ We didn’t know all requirements in advance ‣ Requirements are subject to change (continuously) ‣ There is no “right way” ‣ Non-technical requirements (i.e. organization structure) are rarely portable
  9. Started From… ‣ JSON-RPC 2.0 protocol over HTTP transport ‣

    Server: jetty & ring ‣ Service: implicit, ad-hoc definition, code copy & paste ‣ Deploy: JAR (uber), upstart, fab ‣ Discovery: URI with environment variables ‣ Security: HMAC request signature
  10. Next steps (2) ‣ Deployment procedure: -move all fab commands

    to shared library -save uberjars (each version) on S3 -ping and http-based health checker -report all activity to Slack -run:as (to connect local service to QA or Prod clusters)
  11. Next steps (3) ‣ Switched to httpkit (http server &

    client) -better benchmarks but not really applicable for our case -wanted to use core.async for service definitions, but still using futures (it’s ok for us)
  12. More Services, New Problems ‣ logs: unification, collect/process ‣ errors

    tracking: new type of errors (inter-service communication) ‣ auth: different levels and procedures ‣ metrics: collect, view, analyze ‣ protocol: dynamic typing is hard to scale
  13. Solutions So Far ‣ logs: used Loggly, not really a

    problem now ‣ errors tracking: Rollbar for failure reports, either abstraction, timeouts handling as first class citizen ‣ metrics: used Graphite, now using InfluxDB ‣ protocol: schema library to params definition and validation
  14. Augustine ‣ shared library with s3-wagon ‣ defservice macro that

    uses multimethod ‣ protocol definition/validation with schema ‣ auth level specification and control ‣ errors, exceptions and timeouts handling ‣ meta information, req/resp ID with flake algorithm
  15. Java ‣ You don’t need to know Java to write

    Clojure ‣ Your http server/framework is written in Java ‣ “Java in Clojure” is easier than “Java in Java” ‣ Most probably you will deal with Java code somehow ‣ GC is your “good but very unpredictable” friend
  16. Java We Use ‣ io operations, streaming & buffering ‣

    XLS reader ‣ base64 ‣ timers ‣ java.text.SimpleDateFormat
  17. Data Communication ‣ Databases: Riak, Redis, CouchDB, PostgreSQL ‣ Our

    Clojure services are “data-centric” (mostly about data manipulations) ‣ “single data responsibility” sounds good, but doesn’t work in our case ‣ Databases are used a lot for cross-service communications to decrease inter-services coupling
  18. Actual Problems ‣ scaling is a hard problem even with

    best instruments ‣ there is no “critical” problem that we can’t solve ‣ there is a big room for enhancements ‣ there is even bigger room for experiments
  19. Investigations (1) ‣ active investigations ‣ errors processing (even with

    either, trying monads) ‣ distributed tracing (partially solved with req IDs) ‣ service discovery & (smart) load balancing ‣ binary protocol & TCP for inter-server communication
  20. Investigations (2) ‣ not really active investigations ‣ auto-generated SDKs

    ‣ back pressure control (looking at Hystrix) ‣ core.async (long story) ‣ tasks cancellation
  21. core.async (1) ‣ Your Server as a Transducer ‣ augustine

    library accepts channel as a return type ‣ httpkit provides async interface ‣ but… futures work fine for us (still?) ‣ still experimenting…
  22. core.async (2) ‣ better timeouts ‣ better multiplexing ‣ easier

    to deal with back-pressure control ‣ async abstractions are very leaky ‣ should reimplement most parts of the code ‣ hard to debug (just like futures)
  23. Finagle-Clojure ‣ github.com/finagle/finagle-clojure ‣ good interface to work with Thrift

    ‣ easy to start with basic template and docs ‣ inconvenient Scala runtime ‣ not-really-idiomatic Clojure ‣ no more comments for now (not using in production)
  24. Thoughts ‣ No regrets about our technical decision(s) ‣ We

    have time to solve problems & concerns ‣ Clojure is ok for product development ‣ Clojure is ok when supporting old code ‣ Clojure development is hard when # engineers > 1** ‣ ** it’s hard to work with people in any case