141
DON’T BLOCK YOURSELF Flavio W. Brasil - SoundCloud

ScalaDays Amsterdam - Don't block yourself

Embed Size (px)

Citation preview

  • DONT BLOCK YOURSELFFlavio W. Brasil - SoundCloud

  • @flaviowbrasil

    @fwbrasil

    backend engineer

  • Service composition

    getclump/clump

  • Service composition Distributed Durable STM

    getclump/clump fwbrasil/activate

  • Service composition Distributed Durable STM Sane scala reflection

    getclump/clump fwbrasil/activate fwbrasil/smirror

  • Service composition Distributed Durable STM Sane scala reflection Type-level validation

    getclump/clump fwbrasil/activate fwbrasil/smirror

    fwbrasil/bond

  • Service composition Distributed Durable STM Sane scala reflection Type-level validation Type-safe REST

    getclump/clump fwbrasil/activate fwbrasil/smirror

    fwbrasil/bond fwbrasil/zoot

  • 12 hours of new content/minute

  • 12 hours of new content/minute 350 million users/month

  • 12 hours of new content/minute 350 million users/month

    1 outer space user

  • 1) WHY NON-BLOCKING? !

    2) FUTURES AND PROMISES !

    3) YOUR SERVER AS A FUNCTION

  • PART ONE !

    WHY NON-BLOCKING?

  • How many requests?

  • TimelineAPI

  • TimelineAPI

  • TimelineAPI

  • TimelineAPI

  • TimelineAPI

  • TimelineAPI

  • TimelineAPI

  • TimelineAPI

    Blocking IO

  • TimelineAPI

    Blocking IO

  • TimelineAPI

    Blocking IO

  • Threads aren't free!

  • Threads aren't free!

    3 facts

  • Java uses native threads1

  • The stack uses memory2

  • Garbage collection pressure

    3

  • GC pressure

    Java uses native threads

    The stack uses memory

  • Multiplexing!

  • Waiters aren't free!

  • Thread

  • Thread

    EventLoop

  • TimelineAPI

  • TimelineAPI

    timeline.stream(u): List[Event]

  • TimelineAPI

  • TimelineAPI

    timeline.stream(u, callback)

  • TimelineAPI

    timeline.stream(u): Future[List[Event]]

  • PART TWO !

    FUTURES AND PROMISES

  • Future[List[Event]]

  • Reference for an asynchronous operation completion

    Future[List[Event]]

  • Returns immediately

    timeline .stream(u): Future[List[Event]]

  • future .poll: Option[Try[List[Event]]]

  • !

    val filtered: Future[List[Event]] = timeline.stream(u).map { _.filter(_.isTrackPost) }

    Compose a new future

  • !

    val filtered: Future[List[Event]] = timeline.stream(u).map { _.filter(_.isTrackPost) }

    Compose a new future

  • !

    val filtered: Future[List[Event]] = timeline.stream(u).map { _.filter(_.isTrackPost) }

    Compose a new future

  • Compose a new future using another future

    timeline.stream(u).flatMap { fetchTrackMetadata(_)}

  • val session = authenticate(request)!

    val geolocation = geolocate(request)

  • Parallel

    val session = authenticate(request)!

    val geolocation = geolocate(request)

  • !

    session.join(geolocation).map { case (session, geo) => doSomething}

    Compose parallel futures

  • !

    session.join(geolocation).map { case (session, geo) => doSomething}

    Compose parallel futures

  • !

    val tracks: Seq[Future[Track]] = ids.map(tracksService.fetch)!

    val collect: Future[Seq[Track]] = Future.collect(tracks)

    Compose parallel futures

  • !

    val tracks: Seq[Future[Track]] = ids.map(tracksService.fetch)!

    val collect: Future[Seq[Track]] = Future.collect(tracks)

    Compose parallel futures

  • Recursive composition

  • Recursive composition

    def likes(user: Urn, offset: Int = 0) = likesService.fetch(user, offset).flatMap { case page if(page.hasNext) => likes(user, page.nextOffset).map { page.events ++ _ } case page => Future.value(page.events) }

  • Recursive composition

    def likes(user: Urn, offset: Int = 0) = likesService.fetch(user, offset).flatMap { case page if(page.hasNext) => likes(user, page.nextOffset).map { page.events ++ _ } case page => Future.value(page.events) }

  • Recursive composition

    def likes(user: Urn, offset: Int = 0) = likesService.fetch(user, offset).flatMap { case page if(page.hasNext) => likes(user, page.nextOffset).map { page.events ++ _ } case page => Future.value(page.events) }

  • Recursive composition

    def likes(user: Urn, offset: Int = 0) = likesService.fetch(user, offset).flatMap { case page if(page.hasNext) => likes(user, page.nextOffset).map { page.events ++ _ } case page => Future.value(page.events) }

  • Recursive composition

    def likes(user: Urn, offset: Int = 0) = likesService.fetch(user, offset).flatMap { case page if(page.hasNext) => likes(user, page.nextOffset).map { page.events ++ _ } case page => Future.value(page.events) }

  • Recursive composition

    def likes(user: Urn, offset: Int = 0) = likesService.fetch(user, offset).flatMap { case page if(page.hasNext) => likes(user, page.nextOffset).map { page.events ++ _ } case page => Future.value(page.events) }

  • !

    timeline.stream(u).flatMap { events => Future.collect(Seq( fetchTracks(events), fetchPlaylists(events), fetchComments(events), fetchUsers(events)))}

  • !

    timeline.stream(u).flatMap { events => Future.collect(Seq( fetchTracks(events), fetchPlaylists(events), fetchComments(events), fetchUsers(events)))}

  • Timeout

    !

    timeline.stream(u).flatMap { events => Future.collect(Seq( fetchTracks(events), fetchPlaylists(events), fetchComments(events), fetchUsers(events)))}.within(2.seconds)

  • Cancellation

    !

    timeline.stream(u).flatMap { events => Future.collect(Seq( fetchTracks(events), fetchPlaylists(events), fetchComments(events), fetchUsers(events)))}.within(2.seconds)

  • Local values

  • !

    object Context { val requestId = new Local[Int]}!

    def endpoint(request: Request) = callService1 .map(applyATransformation(_)) .flatMap(callService2) timeline.stream(u, Context.requestId())

    Local values

  • !

    object Context { val requestId = new Local[Int]}!

    def endpoint(request: Request) = callService1 .map(applyATransformation(_)) .flatMap(callService2) timeline.stream(u, Context.requestId())

    Local values

  • !

    object Context { val requestId = new Local[Int]}!

    def endpoint(request: Request) = callService1 .map(applyATransformation(_)) .flatMap(callService2) timeline.stream(u, Context.requestId())

    Local values

  • Promise[Stream]

  • A promise is a writable future

    Promise[Stream]

  • val promise = Promise[List[Event]]()promise.setValue(List())

  • Not common to use

    val promise = Promise[List[Event]]()promise.setValue(List())

  • PART THREE !

    YOUR SERVER AS A FUNCTION

  • The tripod

  • Futures

    Services

    Filters

  • Futures

  • Services

  • Servicestype Service[Req, Rep] = Req => Future[Rep]

  • Services

    Asynchronous functions that represent systems boundaries

    type Service[Req, Rep] = Req => Future[Rep]

  • Services

    Symmetric and uniform API for clients and servers

    type Service[Req, Rep] = Req => Future[Rep]

  • Services

    val client: Service[HttpReq, HttpRep] = !val f: Future[HttpRep] = client(HttpReq("/"))

  • Services

    val client: Service[HttpReq, HttpRep] = !val f: Future[HttpRep] = client(HttpReq("/"))

  • Services

    val client: Service[HttpReq, HttpRep] = !val f: Future[HttpRep] = client(HttpReq("/"))

    val server: Service[HttpReq, HttpRep] = !Http.serve(":80", service)

  • Services

    val client: Service[HttpReq, HttpRep] = !val f: Future[HttpRep] = client(HttpReq("/"))

    val server: Service[HttpReq, HttpRep] = !Http.serve(":80", service)

  • one-liner proxy

  • Http.serve(:81", Http.newClient(127.0.0.1:80))

    one-liner proxy

  • Filterstype Filter[Req, Rep] = (Req, Service[Req, Rep]) => Future[Rep]

  • Filterstype Filter[Req, Rep] = (Req, Service[Req, Rep]) => Future[Rep]

    Application agnostic concerns (timeout, retry, etc)

  • Filtersclass TimeoutFilter[Req, Rep] extends SimpleFilter[Req, Rep] { def apply(req: Req, service: Service[Req, Rep]) = service(req).within(2.seconds)}

  • Filters

    val serviceWithTimeout = new TimeoutFilter andThen service

    class TimeoutFilter[Req, Rep] extends SimpleFilter[Req, Rep] { def apply(req: Req, service: Service[Req, Rep]) = service(req).within(2.seconds)}

  • Not only a tripod

  • Not only NIO

  • - Connection pooling

  • - Connection pooling - Load-balancers

  • - Connection pooling - Load-balancers - Distributed tracing

  • - Connection pooling - Load-balancers - Distributed tracing - Service discovery

  • - Connection pooling - Load-balancers - Distributed tracing - Service discovery - Metrics

  • REACTIVE!!!!!!!1111

  • - Failover strategies

  • - Failover strategies - Failure detectors

  • - Failover strategies - Failure detectors - Dynamic cluster

  • - Failover strategies - Failure detectors - Dynamic cluster - Back-pressure

  • MUX

  • MUX

    GC avoidance

  • Failure detector

  • kraken example

    Errors / second

    Failure detector

  • kraken example

    Errors / second

    Failure detector

  • CONCLUSIONS

  • - Can't use scala futures !

    !

  • - Can't use scala futures - slow adoption of new scala versions

  • - Can't use scala futures - slow adoption of new scala versions - netty 3

  • - Can't use scala futures - slow adoption of new scala versions - netty 3 - Internals are complex

  • - Can't use scala futures - slow adoption of new scala versions - netty 3 - Internals are complex - Mysql driver is limited

  • Happy user

  • - Really good toolchain - - -

  • - Really good toolchain - Many protocols - -

  • - Really good toolchain - Many protocols - Active community -

  • - Really good toolchain - Many protocols - Active community - Battle tested

  • DONT BLOCK YOURSELFFlavio W. Brasil - SoundCloud