All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.metamx.tranquility.druid.DruidBeam.scala Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to Metamarkets Group Inc. (Metamarkets) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  Metamarkets licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package com.metamx.tranquility.druid

import com.github.nscala_time.time.Imports._
import com.metamx.common.scala.Logging
import com.metamx.common.scala.Predef._
import com.metamx.emitter.service.ServiceEmitter
import com.metamx.tranquility.beam.Beam
import com.metamx.tranquility.beam.DefunctBeamException
import com.metamx.tranquility.beam.SendResult
import com.metamx.tranquility.finagle._
import com.metamx.tranquility.security._
import com.metamx.tranquility.typeclass.ObjectWriter
import com.twitter.finagle.{http, Service}
import com.twitter.io.Buf
import com.twitter.util._

/**
  * A Beam that writes all messages to a fixed set of Druid tasks.
  */
class DruidBeam[A](
  private[druid] val interval: Interval,
  private[druid] val partition: Int,
  private[druid] val tasks: Seq[TaskPointer],
  location: DruidLocation,
  config: DruidBeamConfig,
  taskLocator: TaskLocator,
  indexService: IndexService,
  emitter: ServiceEmitter,
  objectWriter: ObjectWriter[A]
) extends Beam[A] with Logging with Closable
{
  private[this] val clients = Map(
    tasks map {
      task =>
        task ->
          new TaskClient(
            task,
            BasicAuthClientMaker.wrapBaseClient(
              taskLocator.connect(task),
              Some(config.basicAuthUser),
              Some(config.basicAuthPass)
            ),
            location.dataSource,
            config.firehoseQuietPeriod,
            config.firehoseRetryPeriod,
            indexService,
            emitter
          )
    }: _*
  )

  override def sendAll(messages: Seq[A]): Seq[Future[SendResult]] = {
    val messagesWithPromises = Vector() ++ messages.map(message => (message, Promise[SendResult]()))

    // Messages grouped into chunks
    val messagesChunks: List[(Array[Byte], IndexedSeq[(A, Promise[SendResult])])] = messagesWithPromises
      .grouped(config.firehoseChunkSize)
      .map(xs => (objectWriter.batchAsBytes(xs.map(_._1)), xs))
      .toList

    for ((messagesChunkBytes, messagesChunk) <- messagesChunks) {
      // Try to send to all tasks, return "sent" if any of them accepted it.
      val taskResponses: Seq[Future[(TaskPointer, SendResult)]] = for {
        task <- tasks
        client <- clients.get(task) if client.active
      } yield {
        val messagePost = HttpPost(
          "/druid/worker/v1/chat/%s/push-events" format
            (location.environment.firehoseServicePattern format task.serviceKey)
        ) withEffect {
          req =>
            req.headerMap("Content-Type") = objectWriter.contentType
            req.headerMap("Content-Length") = messagesChunkBytes.length.toString
            req.content = Buf.ByteArray.Shared(messagesChunkBytes)
        }
        if (log.isTraceEnabled) {
          log.trace(
            "Sending %,d messages to task[%s], firehose[%s]: %s",
            messagesChunk.size,
            task.id,
            task.serviceKey,
            new String(messagesChunkBytes)
          )
        }
        client(messagePost) map {
          case Some(response) => task -> SendResult.Sent
          case None => task -> SendResult.Dropped
        }
      }

      // Get the SendResult for this chunk.
      val chunkResult: Future[SendResult] = Future.collect(taskResponses) map { responses =>
        responses collectFirst {
          case (taskPointer, taskResult) if taskResult.sent => taskResult
        } getOrElse {
          // Nothing failed (or else Future.collect would have returned a failed future) but also nothing sent.
          // This means all tasks must be gone.
          throw new DefunctBeamException("Tasks are all gone: %s" format tasks.map(_.id).mkString(", "))
        }
      }

      // Avoid become(chunkResult), for some reason it creates massive promise chains.
      chunkResult respond {
        case Return(result) => messagesChunk.foreach(_._2.setValue(result))
        case Throw(e) => messagesChunk.foreach(_._2.setException(e))
      }
    }

    messagesWithPromises.map(_._2)
  }

  override def close(deadline: Time): Future[Unit] = {
    log.info(
      "Closing Druid beam for datasource[%s] interval[%s] (tasks = %s)",
      location.dataSource,
      interval,
      tasks.map(_.id).mkString(", ")
    )
    Future.collect(clients.values.toList.map(client => client.close(deadline))) map (_ => ())
  }

  override def toString = "DruidBeam(interval = %s, partition = %s, tasks = [%s])" format
    (interval, partition, clients.values.map(t => "%s/%s" format(t.task.id, t.task.serviceKey)).mkString("; "))
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy