Ingestion

    In most ingestion methods, the work of loading data is done by Druid MiddleManager processes (or the processes). One exception is Hadoop-based ingestion, where this work is instead done using a Hadoop MapReduce job on YARN (although MiddleManager or Indexer processes are still involved in starting and monitoring the Hadoop jobs). Once segments have been generated and stored in deep storage, they will be loaded by Historical processes. For more details on how this works under the hood, see the section of Druid’s design documentation.

    How to use this documentation

    This page you are currently reading provides information about universal Druid ingestion concepts, and about configurations that are common to all .

    The individual pages for each ingestion method provide additional information about concepts and configurations that are unique to each ingestion method.

    We recommend reading (or at least skimming) this universal page first, and then referring to the page for the ingestion method or methods that you have chosen.

    Ingestion methods

    The table below lists Druid’s most common data ingestion methods, along with comparisons to help you choose the best one for your situation. Each ingestion method supports its own set of source systems to pull from. For details about how each method works, as well as configuration properties specific to that method, check out its documentation page.

    The most recommended, and most popular, method of streaming ingestion is the that reads directly from Kafka. The Kinesis indexing service also works well if you prefer Kinesis.

    This table compares the major available options:

    Batch

    When doing batch loads from files, you should use one-time , and you have three options: index_parallel (native batch; parallel), index_hadoop (Hadoop-based), or index (native batch; single-task).

    In general, we recommend native batch whenever it meets your needs, since the setup is simpler (it does not depend on an external Hadoop cluster). However, there are still scenarios where Hadoop-based batch ingestion might be a better choice, for example when you already have a running Hadoop cluster and want to use the cluster resource of the existing cluster for batch ingestion.

    This table compares the three available options:

    MethodNative batch (parallel)Native batch (simple)
    Task typeindex_parallelindex_hadoopindex
    Parallel?Yes, if inputFormat is splittable and maxNumConcurrentSubTasks > 1 in tuningConfig. See for details.Yes, always.No. Each task is single-threaded.
    Can append or overwrite?Yes, both.Overwrite only.Yes, both.
    External dependenciesNone.Hadoop cluster (Druid submits Map/Reduce jobs).None.
    Input locationsAny inputSource.Any Hadoop FileSystem or Druid datasource.Any .
    File formatsAny inputFormat.Any Hadoop InputFormat.Any .
    Rollup modesPerfect if forceGuaranteedRollup = true in the .Always perfect.Perfect if forceGuaranteedRollup = true in the tuningConfig.
    Partitioning optionsDynamic, hash-based, and range-based partitioning methods are available. See for details.Hash-based or range-based partitioning via partitionsSpec.Dynamic and hash-based partitioning methods are available. See for details.

    Datasources

    Druid data is stored in datasources, which are similar to tables in a traditional RDBMS. Druid offers a unique data modeling system that bears similarity to both relational and timeseries models.

    Primary timestamp

    Druid schemas must always include a primary timestamp. The primary timestamp is used for partitioning and sorting your data. Druid queries are able to rapidly identify and retrieve data corresponding to time ranges of the primary timestamp column. Druid is also able to use the primary timestamp column for time-based such as dropping time chunks, overwriting time chunks, and time-based retention rules.

    The primary timestamp is parsed based on the timestampSpec. In addition, the controls other important operations that are based on the primary timestamp. Regardless of which input field the primary timestamp is read from, it will always be stored as a column named __time in your Druid datasource.

    If you have more than one timestamp column, you can store the others as secondary timestamps.

    Dimensions

    Dimensions are columns that are stored as-is and can be used for any purpose. You can group, filter, or apply aggregators to dimensions at query time in an ad-hoc manner. If you run with rollup disabled, then the set of dimensions is simply treated like a set of columns to ingest, and behaves exactly as you would expect from a typical database that does not support a rollup feature.

    Dimensions are configured through the .

    Metrics

    Metrics are columns that are stored in an aggregated form. They are most useful when is enabled. Specifying a metric allows you to choose an aggregation function for Druid to apply to each row during ingestion. This has two benefits:

    1. If rollup is enabled, multiple rows can be collapsed into one row even while retaining summary information. In the , this is used to collapse netflow data to a single row per (minute, srcIP, dstIP) tuple, while retaining aggregate information about total packet and byte counts.
    2. Some aggregators, especially approximate ones, can be computed faster at query time even on non-rolled-up data if they are partially computed at ingestion time.

    Metrics are configured through the metricsSpec.

    Rollup

    Druid can roll up data as it is ingested to minimize the amount of raw data that needs to be stored. Rollup is a form of summarization or pre-aggregation. In practice, rolling up data can dramatically reduce the size of data that needs to be stored, reducing row counts by potentially orders of magnitude. This storage reduction does come at a cost: as we roll up data, we lose the ability to query individual events.

    When rollup is disabled, Druid loads each row as-is without doing any form of pre-aggregation. This mode is similar to what you would expect from a typical database that does not support a rollup feature.

    When rollup is enabled, then any rows that have identical dimensions and to each other (after queryGranularity-based truncation) can be collapsed, or rolled up, into a single row in Druid.

    By default, rollup is enabled.

    Enabling or disabling rollup

    Rollup is controlled by the rollup setting in the granularitySpec. By default, it is true (enabled). Set this to false if you want Druid to store each record as-is, without any rollup summarization.

    Example of rollup

    For an example of how to configure rollup, and of how the feature will modify your data, check out the rollup tutorial.

    Maximizing rollup ratio

    You can measure the rollup ratio of a datasource by comparing the number of rows in Druid with the number of ingested events. The higher this number, the more benefit you are gaining from rollup. One way to do this is with a Druid SQL query like:

    In this query, cnt should refer to a “count” type metric specified at ingestion time. See on the “Schema design” page for more details about how counting works when rollup is enabled.

    Tips for maximizing rollup:

    • Generally, the fewer dimensions you have, and the lower the cardinality of your dimensions, the better rollup ratios you will achieve.
    • Use sketches to avoid storing high cardinality dimensions, which harm rollup ratios.
    • Adjusting queryGranularity at ingestion time (for example, using PT5M instead of PT1M) increases the likelihood of two rows in Druid having matching timestamps, and can improve your rollup ratios.
    • It can be beneficial to load the same data into more than one Druid datasource. Some users choose to create a “full” datasource that has rollup disabled (or enabled, but with a minimal rollup ratio) and an “abbreviated” datasource that has fewer dimensions and a higher rollup ratio. When queries only involve dimensions in the “abbreviated” set, using that datasource leads to much faster query times. This can often be done with just a small increase in storage footprint, since abbreviated datasources tend to be substantially smaller.
    • If you are using a ingestion configuration that does not guarantee perfect rollup, you can potentially improve your rollup ratio by switching to a guaranteed perfect rollup option, or by reindexing your data in the background after initial ingestion.

    Perfect rollup vs Best-effort rollup

    Some Druid ingestion methods guarantee perfect rollup, meaning that input data are perfectly aggregated at ingestion time. Others offer best-effort rollup, meaning that input data might not be perfectly aggregated and thus there could be multiple segments holding rows with the same timestamp and dimension values.

    In general, ingestion methods that offer best-effort rollup do this because they are either parallelizing ingestion without a shuffling step (which would be required for perfect rollup), or because they are finalizing and publishing segments before all data for a time chunk has been received, which we call incremental publishing. In both of these cases, records that could theoretically be rolled up may end up in different segments. All types of streaming ingestion run in this mode.

    The following table shows how each method handles rollup:

    MethodHow it works
    Native batchindex_parallel and index type may be either perfect or best-effort, based on configuration.
    Always perfect.
    Kafka indexing serviceAlways best-effort.
    Always best-effort.

    Partitioning

    Why partition?

    Optimal partitioning and sorting of segments within your datasources can have substantial impact on footprint and performance.

    Druid datasources are always partitioned by time into time chunks, and each time chunk contains one or more segments. This partitioning happens for all ingestion methods, and is based on the segmentGranularity parameter of your ingestion spec’s dataSchema.

    The segments within a particular time chunk may also be partitioned further, using options that vary based on the ingestion type you have chosen. In general, doing this secondary partitioning using a particular dimension will improve locality, meaning that rows with the same value for that dimension are stored together and can be accessed quickly.

    You will usually get the best performance and smallest overall footprint by partitioning your data on some “natural” dimension that you often filter by, if one exists. This will often improve compression - users have reported threefold storage size decreases - and it also tends to improve query performance as well.

    Not all ingestion methods support an explicit partitioning configuration, and not all have equivalent levels of flexibility. As of current Druid versions, If you are doing initial ingestion through a less-flexible method (like Kafka) then you can use reindexing techniques to repartition your data after it is initially ingested. This is a powerful technique: you can use it to ensure that any data older than a certain threshold is optimally partitioned, even as you continuously add new data from a stream.

    The following table shows how each ingestion method handles partitioning:

    Note that, of course, one way to partition data is to load it into separate datasources. This is a perfectly viable approach and works very well when the number of datasources does not lead to excessive per-datasource overheads. If you go with this approach, then you can ignore this section, since it is describing how to set up partitioning within a single datasource.

    For more details on splitting data up into separate datasources, and potential operational considerations, refer to the page.

    No matter what ingestion method you use, data is loaded into Druid using either one-time tasks or ongoing “supervisors” (which run and supervise a set of tasks over time). In any case, part of the task or supervisor definition is an ingestion spec.

    Ingestion specs consists of three main components:

    Example ingestion spec for task type index_parallel (native batch):

    1. {
    2. "type": "index_parallel",
    3. "spec": {
    4. "dataSchema": {
    5. "dataSource": "wikipedia",
    6. "timestampSpec": {
    7. "column": "timestamp",
    8. "format": "auto"
    9. },
    10. "dimensionsSpec": {
    11. "dimensions": [
    12. { "type": "string", "page" },
    13. { "type": "string", "language" },
    14. { "type": "long", "name": "userId" }
    15. ]
    16. },
    17. "metricsSpec": [
    18. { "type": "count", "name": "count" },
    19. { "type": "doubleSum", "name": "bytes_added_sum", "fieldName": "bytes_added" },
    20. { "type": "doubleSum", "name": "bytes_deleted_sum", "fieldName": "bytes_deleted" }
    21. "granularitySpec": {
    22. "segmentGranularity": "day",
    23. "queryGranularity": "none",
    24. "intervals": [
    25. "2013-08-31/2013-09-01"
    26. ]
    27. }
    28. },
    29. "ioConfig": {
    30. "type": "index_parallel",
    31. "inputSource": {
    32. "type": "local",
    33. "baseDir": "examples/indexing/",
    34. "filter": "wikipedia_data.json"
    35. },
    36. "inputFormat": {
    37. "type": "json",
    38. "flattenSpec": {
    39. "useFieldDiscovery": true,
    40. "fields": [
    41. { "type": "path", "name": "userId", "expr": "$.user.id" }
    42. ]
    43. }
    44. }
    45. },
    46. "tuningConfig": {
    47. "type": "index_parallel"
    48. }
    49. }
    50. }

    The specific options supported by these sections will depend on the you have chosen. For more examples, refer to the documentation for each ingestion method.

    You can also load data visually, without the need to write an ingestion spec, using the “Load data” functionality available in Druid’s web console. Druid’s visual data loader supports , Kinesis, and mode.

    dataSchema

    The dataSchema spec has been changed in 0.17.0. The new spec is supported by all ingestion methods except for Hadoop ingestion. See the for the old spec.

    The dataSchema is a holder for the following components:

    An example dataSchema is:

    1. "dataSchema": {
    2. "dataSource": "wikipedia",
    3. "timestampSpec": {
    4. "column": "timestamp",
    5. "format": "auto"
    6. },
    7. "dimensionsSpec": {
    8. "dimensions": [
    9. { "type": "string", "page" },
    10. { "type": "string", "language" },
    11. { "type": "long", "name": "userId" }
    12. ]
    13. },
    14. "metricsSpec": [
    15. { "type": "count", "name": "count" },
    16. { "type": "doubleSum", "name": "bytes_added_sum", "fieldName": "bytes_added" },
    17. ],
    18. "granularitySpec": {
    19. "segmentGranularity": "day",
    20. "queryGranularity": "none",
    21. "intervals": [
    22. "2013-08-31/2013-09-01"
    23. ]
    24. }
    25. }

    dataSource

    The dataSource is located in dataSchemadataSource and is simply the name of the datasource that data will be written to. An example dataSource is:

    1. "dataSource": "my-first-datasource"

    timestampSpec

    The timestampSpec is located in dataSchematimestampSpec and is responsible for configuring the primary timestamp. An example timestampSpec is:

    A timestampSpec can have the following components:

    FieldDescriptionDefault
    columnInput row field to read the primary timestamp from.

    Regardless of the name of this input field, the primary timestamp will always be stored as a column named __time in your Druid datasource.
    timestamp
    formatTimestamp format. Options are:
    • iso: ISO8601 with ‘T’ separator, like “2000-01-01T01:02:03.456”
    • posix: seconds since epoch
    • millis: milliseconds since epoch
    • micro: microseconds since epoch
    • nano: nanoseconds since epoch
    • auto: automatically detects ISO (either ‘T’ or space separator) or millis format
    • any
    auto
    missingValueTimestamp to use for input records that have a null or missing timestamp column. Should be in ISO8601 format, like “2000-01-01T01:02:03.456”, even if you have specified something else for format. Since Druid requires a primary timestamp, this setting can be useful for ingesting datasets that do not have any per-record timestamps at all.none

    dimensionsSpec

    The dimensionsSpec is located in dataSchemadimensionsSpec and is responsible for configuring . An example dimensionsSpec is:

    1. "dimensionsSpec" : {
    2. "dimensions": [
    3. "language",
    4. { "type": "long", "name": "userId" }
    5. ],
    6. "dimensionExclusions" : [],
    7. "spatialDimensions" : []
    8. }

    Conceptually, after input data records are read, Druid applies ingestion spec components in a particular order: first flattenSpec (if any), then , then transformSpec, and finally and metricsSpec. Keep this in mind when writing your ingestion spec.

    A dimensionsSpec can have the following components:

    FieldDescriptionDefault
    dimensionsA list of . Cannot have the same column in both dimensions and dimensionExclusions.

    If this and spatialDimensions are both null or empty arrays, Druid will treat all non-timestamp, non-metric columns that do not appear in dimensionExclusions as String-typed dimension columns. See inclusions and exclusions below for details.
    []
    dimensionExclusionsThe names of dimensions to exclude from ingestion. Only names are supported here, not objects.

    This list is only used if the dimensions and spatialDimensions lists are both null or empty arrays; otherwise it is ignored. See below for details.
    []
    spatialDimensionsAn array of spatial dimensions.[]

    Dimension objects

    Each dimension in the dimensions list can either be a name or an object. Providing a name is equivalent to providing a string type dimension object with the given name, e.g. "page" is equivalent to {"name": "page", "type": "string"}.

    Dimension objects can have the following components:

    Inclusions and exclusions

    Druid will interpret a dimensionsSpec in two possible ways: normal or schemaless.

    Schemaless interpretation occurs when both dimensions and spatialDimensions are empty or null. In this case, the set of dimensions is determined in the following way:

    1. First, start from the set of all input fields from the (or the flattenSpec, if one is being used).
    2. Any field listed in dimensionExclusions is excluded.
    3. The field listed as column in the is excluded.
    4. Any field used as an input to an aggregator from the metricsSpec is excluded.
    5. Any field with the same name as an aggregator from the is excluded.
    6. All other fields are ingested as string typed dimensions with the default settings.

    Note: Fields generated by a are not currently considered candidates for schemaless dimension interpretation.

    metricsSpec

    The metricsSpec is located in dataSchemametricsSpec and is a list of to apply at ingestion time. This is most useful when rollup is enabled, since it’s how you configure ingestion-time aggregation.

    An example metricsSpec is:

    1. "metricsSpec": [
    2. { "type": "count", "name": "count" },
    3. { "type": "doubleSum", "name": "bytes_added_sum", "fieldName": "bytes_added" },
    4. { "type": "doubleSum", "name": "bytes_deleted_sum", "fieldName": "bytes_deleted" }
    5. ]

    granularitySpec

    The granularitySpec is located in dataSchemagranularitySpec and is responsible for configuring the following operations:

    1. Partitioning a datasource into time chunks (via segmentGranularity).
    2. Truncating the timestamp, if desired (via queryGranularity).
    3. Specifying which time chunks of segments should be created, for batch ingestion (via intervals).
    4. Specifying whether ingestion-time should be used or not (via rollup).

    Other than rollup, these operations are all based on the primary timestamp.

    An example granularitySpec is:

    1. "granularitySpec": {
    2. "segmentGranularity": "day",
    3. "queryGranularity": "none",
    4. "intervals": [
    5. "2013-08-31/2013-09-01"
    6. ],
    7. "rollup": true
    8. }

    A granularitySpec can have the following components:

    FieldDescriptionDefault
    typeEither uniform or arbitrary. In most cases you want to use uniform.uniform
    segmentGranularity granularity for this datasource. Multiple segments can be created per time chunk. For example, when set to day, the events of the same day fall into the same time chunk which can be optionally further partitioned into multiple segments based on other configurations and input size. Any granularity can be provided here. Note that all segments in the same time chunk should have the same segment granularity.

    Ignored if type is set to arbitrary.
    day
    queryGranularityThe resolution of timestamp storage within each segment. This must be equal to, or finer, than segmentGranularity. This will be the finest granularity that you can query at and still receive sensible results, but note that you can still query at anything coarser than this granularity. E.g., a value of minute will mean that records will be stored at minutely granularity, and can be sensibly queried at any multiple of minutes (including minutely, 5-minutely, hourly, etc).

    Any can be provided here. Use none to store timestamps as-is, without any truncation. Note that rollup will be applied if it is set even when the queryGranularity is set to none.
    none
    rollupWhether to use ingestion-time rollup or not. Note that rollup is still effective even when queryGranularity is set to . Your data will be rolled up if they have the exactly same timestamp.true
    intervalsA list of intervals describing what time chunks of segments should be created. If type is set to uniform, this list will be broken up and rounded-off based on the segmentGranularity. If type is set to arbitrary, this list will be used as-is.

    If null or not provided, batch ingestion tasks will generally determine which time chunks to output based on what timestamps are found in the input data.

    If specified, batch ingestion tasks may be able to skip a determining-partitions phase, which can result in faster ingestion. Batch ingestion tasks may also be able to request all their locks up-front instead of one by one. Batch ingestion tasks will throw away any records with timestamps outside of the specified intervals.

    Ignored for any form of streaming ingestion.
    null

    The transformSpec is located in dataSchematransformSpec and is responsible for transforming and filtering records during ingestion time. It is optional. An example transformSpec is:

    Conceptually, after input data records are read, Druid applies ingestion spec components in a particular order: first (if any), then timestampSpec, then , and finally dimensionsSpec and . Keep this in mind when writing your ingestion spec.

    Transforms

    The transforms list allows you to specify a set of expressions to evaluate on top of input data. Each transform has a “name” which can be referred to by your dimensionsSpec, metricsSpec, etc.

    If a transform has the same name as a field in an input row, then it will shadow the original field. Transforms that shadow fields may still refer to the fields they shadow. This can be used to transform a field “in-place”.

    Transforms do have some limitations. They can only refer to fields present in the actual input rows; in particular, they cannot refer to other transforms. And they cannot remove fields, only add them. However, they can shadow a field with another field containing all nulls, which will act similarly to removing the field.

    Transforms can refer to the of an input row by referring to __time as part of the expression. They can also replace the timestamp if you set their “name” to __time. In both cases, __time should be treated as a millisecond timestamp (number of milliseconds since Jan 1, 1970 at midnight UTC). Transforms are applied after the timestampSpec.

    Druid currently includes one kind of built-in transform, the expression transform. It has the following syntax:

    1. {
    2. "type": "expression",
    3. "name": "<output name>",
    4. "expression": "<expr>"
    5. }

    The expression is a Druid query expression.

    Conceptually, after input data records are read, Druid applies ingestion spec components in a particular order: first (if any), then timestampSpec, then , and finally dimensionsSpec and . Keep this in mind when writing your ingestion spec.

    Filter

    The filter conditionally filters input rows during ingestion. Only rows that pass the filter will be ingested. Any of Druid’s standard can be used. Note that within a transformSpec, the transforms are applied before the filter, so the filter can refer to a transform.

    Legacy dataSchema spec

    The legacy dataSchema spec has below two more components in addition to the ones listed in the section above.

    parser (Deprecated)

    In legacy dataSchema, the parser is located in the dataSchemaparser and is responsible for configuring a wide variety of items related to parsing input records. The parser is deprecated and it is highly recommended to use inputFormat instead. For details about inputFormat and supported parser types, see the .

    For details about major components of the parseSpec, refer to their subsections:

    An example parser is:

    1. "parser": {
    2. "type": "string",
    3. "parseSpec": {
    4. "format": "json",
    5. "flattenSpec": {
    6. "useFieldDiscovery": true,
    7. "fields": [
    8. { "type": "path", "name": "userId", "expr": "$.user.id" }
    9. ]
    10. },
    11. "timestampSpec": {
    12. "column": "timestamp",
    13. "format": "auto"
    14. },
    15. "dimensionsSpec": {
    16. "dimensions": [
    17. { "type": "string", "page" },
    18. { "type": "string", "language" },
    19. { "type": "long", "name": "userId" }
    20. ]
    21. }
    22. }
    23. }

    flattenSpec

    In the legacy dataSchema, the flattenSpec is located in dataSchemaparserparseSpecflattenSpec and is responsible for bridging the gap between potentially nested input data (such as JSON, Avro, etc) and Druid’s flat data model. See Flatten spec for more details.

    ioConfig

    The ioConfig influences how data is read from a source system, such as Apache Kafka, Amazon S3, a mounted filesystem, or any other supported source system. The inputFormat property applies to all ingestion method except for Hadoop ingestion. The Hadoop ingestion still uses the in the legacy dataSchema. The rest of ioConfig is specific to each individual ingestion method. An example ioConfig to read JSON data is:

    1. "ioConfig": {
    2. "type": "<ingestion-method-specific type code>",
    3. "inputFormat": {
    4. "type": "json"
    5. },
    6. ...

    For more details, see the documentation provided by each ingestion method.

    Tuning properties are specified in a tuningConfig, which goes at the top level of an ingestion spec. Some properties apply to all , but most are specific to each individual ingestion method. An example tuningConfig that sets all of the shared, common properties to their defaults is:

    FieldDescriptionDefault
    typeEach ingestion method has its own tuning type code. You must specify the type code that matches your ingestion method. Common options are index, hadoop, kafka, and kinesis.
    maxRowsInMemoryThe maximum number of records to store in memory before persisting to disk. Note that this is the number of rows post-rollup, and so it may not be equal to the number of input records. Ingested records will be persisted to disk when either maxRowsInMemory or maxBytesInMemory are reached (whichever happens first).1000000
    maxBytesInMemoryThe maximum aggregate size of records, in bytes, to store in the JVM heap before persisting. This is based on a rough estimate of memory usage. Ingested records will be persisted to disk when either maxRowsInMemory or maxBytesInMemory are reached (whichever happens first).

    Setting maxBytesInMemory to -1 disables this check, meaning Druid will rely entirely on maxRowsInMemory to control memory usage. Setting it to zero means the default value will be used (one-sixth of JVM heap size).

    Note that the estimate of memory usage is designed to be an overestimate, and can be especially high when using complex ingest-time aggregators, including sketches. If this causes your indexing workloads to persist to disk too often, you can set maxBytesInMemory to -1 and rely on maxRowsInMemory instead.
    One-sixth of max JVM heap size
    indexSpecTune how data is indexed. See below for more information.See table below
    Other propertiesEach ingestion method has its own list of additional tuning properties. See the documentation for each method for a full list: Kafka indexing service, , Native batch, and .

    indexSpec

    The indexSpec object can include the following properties: