Index data

You index data using the Elasticsearch REST API. Two APIs exist: the index API and the _bulk API.

For situations in which new data arrives incrementally (for example, customer orders from a small business), you might use the index API to add documents individually as they arrive. For situations in which the flow of data is less frequent (for example, weekly updates to a marketing website), you might prefer to generate a file and send it to the _bulk API. For large numbers of documents, lumping requests together and using the _bulk API offers superior performance. If your documents are enormous, however, you might need to index them individually.

Introduction to indexing

Before you can search data, you must index it. Indexing is the method by which search engines organize data for fast retrieval. The resulting structure is called, fittingly, an index.

In Elasticsearch, the basic unit of data is a JSON document. Within an index, Elasticsearch identifies each document using a unique ID.

A request to the index API looks like the following:

PUT <index>/_doc/<id>
{ "A JSON": "document" }

A request to the _bulk API looks a little different, because you specify the index and ID in the bulk data:

POST _bulk
{ "index": { "_index": "<index>", "_id": "<id>" } }
{ "A JSON": "document" }

Bulk data must conform to a specific format, which requires a newline character (\n) at the end of every line, including the last line. This is the basic format:

Action and metadata\n
Optional document\n
Action and metadata\n
Optional document\n

The document is optional, because delete actions do not require a document. The other actions (index, create, and update) all require a document. If you specifically want the action to fail if the document already exists, use the create action instead of the index action.

To index bulk data using the curl command, navigate to the folder where you have your file saved and run the following command:

curl -H "Content-Type: application/x-ndjson" -POST https://localhost:9200/data/_bulk -u admin:admin --insecure --data-binary "@data.json"

If any one of the actions in the _bulk API fail, Elasticsearch continues to execute the other actions. Examine the items array in the response to figure out what went wrong. The entries in the items array are in the same order as the actions specified in the request.

Elasticsearch features automatic index creation when you add a document to an index that doesn’t already exist. It also features automatic ID generation if you don’t specify an ID in the request. This simple example automatically creates the movies index, indexes the document, and assigns it a unique ID:

POST movies/_doc
{ "title": "Spirited Away" }

Automatic ID generation has a clear downside: because the indexing request didn’t specify a document ID, you can’t easily update the document at a later time. Also, if you run this request 10 times, Elasticsearch indexes this document as 10 different documents with unique IDs. To specify an ID of 1, use the following request, and note the use of PUT instead of POST:

PUT movies/_doc/1
{ "title": "Spirited Away" }

Because you must specify an ID, if you run this command 10 times, you still have just one document indexed with the _version field incremented to 10.

Indices default to one primary shard and one replica. If you want to specify non-default settings, create the index before adding documents:

PUT more-movies
{ "settings": { "number_of_shards": 6, "number_of_replicas": 2 } }

Naming restrictions for indices

Elasticsearch indices have the following naming restrictions:

  • All letters must be lowercase.
  • Index names can’t begin with _ (underscore) or - (hyphen).
  • Index names can’t contain spaces, commas, or the following characters:

    :, ", *, +, /, \, |, ?, #, >, or <

Read data

After you index a document, you can retrieve it by sending a GET request to the same endpoint that you used for indexing:

GET movies/_doc/1

{
  "_index" : "movies",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "_seq_no" : 0,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "title" : "Spirited Away"
  }
}

You can see the document in the _source object. If the document is not found, the found key is false and the _source object is not part of the response.

To retrieve multiple documents with a single command, use the _mget operation. The format for retrieving multiple documents is similar to the _bulk operation, where you must specify the index and ID in the request body:

GET _mget
{
  "docs": [
    {
      "_index": "<index>",
      "_id": "<id>"
    },
    {
      "_index": "<index>",
      "_id": "<id>"
    }
  ]
}

To only return specific fields in a document:

GET _mget
{
  "docs": [
    {
      "_index": "<index>",
      "_id": "<id>",
      "_source": "field1"
    },
    {
      "_index": "<index>",
      "_id": "<id>",
      "_source": "field2"
    }
  ]
}

To check if a document exists:

HEAD movies/_doc/<doc-id>

If the document exists, you get back a 200 OK response, and if it doesn’t, you get back a 404 - Not Found error.

Update data

To update existing fields or to add new fields, send a POST request to the _update operation with your changes in a doc object:

POST movies/_update/1
{
  "doc": {
    "title": "Castle in the Sky",
    "genre": ["Animation", "Fantasy"]
  }
}

Note the updated title field and new genre field:

GET movies/_doc/1

{
  "_index" : "movies",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2,
  "_seq_no" : 1,
  "_primary_term" : 1,
  "found" : true,
  "_source" : {
    "title" : "Castle in the Sky",
    "genre" : [
      "Animation",
      "Fantasy"
    ]
  }
}

The document also has an incremented _version field. Use this field to keep track of how many times a document is updated.

POST requests make partial updates to documents. To altogether replace a document, use a PUT request:

PUT movies/_doc/1
{
  "title": "Spirited Away"
}

The document with ID of 1 will contain only the title field, because the entire document will be replaced with the document indexed in this PUT request.

Use the upsert object to conditionally update documents based on whether they already exist. Here, if the document exists, its title field changes to Castle in the Sky. If it doesn’t, Elasticsearch indexes the document in the upsert object.

POST movies/_update/2
{
  "doc": {
    "title": "Castle in the Sky"
  },
  "upsert": {
    "title": "Only Yesterday",
    "genre": ["Animation", "Fantasy"],
    "date": 1993
  }
}

Sample response

{
  "_index" : "movies",
  "_type" : "_doc",
  "_id" : "2",
  "_version" : 2,
  "result" : "updated",
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 3,
  "_primary_term" : 1
}

Each update operation for a document has a unique combination of the _seq_no and _primary_term values.

Elasticsearch first writes your updates to the primary shard and then sends this change to all the replica shards. An uncommon issue can occur if multiple users of your Elasticsearch-based application make updates to existing documents in the same index. In this situation, another user can read and update a document from a replica before it receives your update from the primary shard. Your update operation then ends up updating an older version of the document. In the best case, you and the other user make the same changes, and the document remains accurate. In the worst case, the document now contains out-of-date information.

To prevent this situation, use the _seq_no and _primary_term values in the request header:

POST movies/_update/2?if_seq_no=3&if_primary_term=1
{
  "doc": {
    "title": "Castle in the Sky",
    "genre": ["Animation", "Fantasy"]
  }
}

If the document is updated after we retrieved it, the _seq_no and _primary_term values are different and our update operation fails with a 409 — Conflict error.

When using the _bulk API, specify the _seq_no and _primary_term values within the action metadata.

Delete data

To delete a document from an index, use a DELETE request:

DELETE movies/_doc/1

The DELETE operation increments the _version field. If you add the document back to the same ID, the _version field increments again. This behavior occurs because Elasticsearch deletes the document _source, but retains its metadata.