ELK Stack — Mappings
Elasticsearch Mappings
A mapping is the schema definition for an Elasticsearch index. It tells Elasticsearch what type each field is — text, keyword, integer, date, boolean, IP, geo_point, etc. — and how it should be indexed and searched.
Why Mappings Matter
Without explicit mappings, Elasticsearch guesses field types when it first sees a document. This dynamic mapping is convenient but often wrong:
// You index this document:
{ "status_code": "200", "response_time": "1.5" }
// Elasticsearch guesses:
// status_code → text (but you want integer!)
// response_time → text (but you want float!)
Now you can’t do status_code > 400 or aggregate on response_time because they’re stored as text, not numbers.
Rule: Always define explicit mappings for production indices.
Core Field Types
| Type | Description | Example Values |
|---|---|---|
text |
Full-text searchable (analyzed) | “Hello world”, “Error in UserService” |
keyword |
Exact value (not analyzed) | “GET”, “error”, “192.168.1.50” |
integer |
32-bit integer | 200, 404, 500 |
long |
64-bit integer | 1234567890 |
float |
32-bit floating point | 1.5, 99.99 |
double |
64-bit floating point | 3.141592653589793 |
boolean |
True or false | true, false |
date |
Date/time | “2025-03-15T10:00:00Z” |
ip |
IPv4 or IPv6 | “192.168.1.50” |
geo_point |
Latitude/Longitude | {“lat”: 40.7, “lon”: -74.0} |
object |
JSON object | {“name”: “John”, “age”: 30} |
nested |
Array of objects (preserves relationship) | [{“name”: “A”}, {“name”: “B”}] |
text vs keyword — The Key Difference
This is the most important distinction in Elasticsearch:
| Aspect | text |
keyword |
|---|---|---|
| Analyzed | Yes (broken into tokens) | No (stored as-is) |
| Use for | Full-text search | Filtering, sorting, aggregations |
| Example | “Error in UserService” → [“error”, “in”, “userservice”] | “GET” → “GET” |
| Search | Match query (fuzzy, partial) | Term query (exact match) |
Example:
{
"message": "Connection timeout in PaymentService", // → text (for searching)
"level": "ERROR", // → keyword (for filtering)
"status_code": 500, // → integer
"host": "web-server-01" // → keyword
}
You’d search message with: “connection timeout” (finds it via full-text search).
You’d filter level with: “ERROR” (exact match only).
Create a Mapping
Define a mapping when creating an index:
$ curl -X PUT -u elastic:changeme123 \
"http://localhost:9200/web-logs" \
-H "Content-Type: application/json" \
-d '{
"mappings": {
"properties": {
"timestamp": { "type": "date" },
"client_ip": { "type": "ip" },
"method": { "type": "keyword" },
"path": { "type": "keyword" },
"status_code": { "type": "integer" },
"bytes_sent": { "type": "long" },
"response_time": { "type": "float" },
"message": { "type": "text" },
"user_agent": { "type": "text" },
"host": { "type": "keyword" },
"geo": {
"properties": {
"country": { "type": "keyword" },
"city": { "type": "keyword" },
"location": { "type": "geo_point" }
}
}
}
},
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0
}
}'
Expected output:
{
"acknowledged": true,
"shards_acknowledged": true,
"index": "web-logs"
}
View a Mapping
$ curl -u elastic:changeme123 \
"http://localhost:9200/web-logs/_mapping?pretty"
Expected output:
{
"web-logs": {
"mappings": {
"properties": {
"bytes_sent": { "type": "long" },
"client_ip": { "type": "ip" },
"host": { "type": "keyword" },
"message": { "type": "text" },
"method": { "type": "keyword" },
"path": { "type": "keyword" },
"response_time": { "type": "float" },
"status_code": { "type": "integer" },
"timestamp": { "type": "date" },
"user_agent": { "type": "text" }
}
}
}
}
Dynamic Mapping
If you index a document without defining a mapping, Elasticsearch uses dynamic mapping to guess types:
| JSON Value | Elasticsearch Guesses |
|---|---|
"hello" |
text + keyword (multi-field) |
123 |
long |
1.5 |
float |
true |
boolean |
"2025-03-15" |
date |
"192.168.1.1" (when using ip type hints) |
text (ES doesn’t auto-detect IP) |
Common mistake: Elasticsearch auto-maps string numbers like "200" as text, not integer. Always define explicit mappings.
Multi-Field Mappings
Sometimes you need a field to be both text (for search) and keyword (for aggregation):
{
"mappings": {
"properties": {
"message": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
}
}
}
}
}
}
Now you can:
- Search with:
message: "connection timeout"(full-text) - Aggregate with:
message.raw(exact values)
Add a Field to an Existing Mapping
You can add new fields but cannot change existing field types (this is an Elasticsearch limitation):
$ curl -X PUT -u elastic:changeme123 \
"http://localhost:9200/web-logs/_mapping" \
-H "Content-Type: application/json" \
-d '{
"properties": {
"datacenter": { "type": "keyword" },
"request_id": { "type": "keyword" }
}
}'
If you need to change a field type, you must:
- Create a new index with the correct mapping
- Reindex data from the old index to the new one
- Delete the old index
- (Optionally) create an alias pointing to the new index
Reindex to Fix a Mapping
# Step 1: Create new index with correct mapping
$ curl -X PUT -u elastic:changeme123 \
"http://localhost:9200/web-logs-v2" \
-H "Content-Type: application/json" \
-d '{
"mappings": {
"properties": {
"status_code": { "type": "integer" }
}
}
}'
# Step 2: Reindex data
$ curl -X POST -u elastic:changeme123 \
"http://localhost:9200/_reindex" \
-H "Content-Type: application/json" \
-d '{
"source": { "index": "web-logs" },
"dest": { "index": "web-logs-v2" }
}'
# Step 3: Delete old index and create alias
$ curl -X DELETE -u elastic:changeme123 "http://localhost:9200/web-logs"
$ curl -X POST -u elastic:changeme123 \
"http://localhost:9200/_aliases" \
-H "Content-Type: application/json" \
-d '{
"actions": [
{ "add": { "index": "web-logs-v2", "alias": "web-logs" } }
]
}'
Date Field Formats
The date field supports multiple formats:
{
"timestamp": {
"type": "date",
"format": "yyyy-MM-dd'T'HH:mm:ss.SSSZ||yyyy-MM-dd||epoch_millis"
}
}
Common date formats:
"2025-03-15T10:00:00.000Z"— ISO 8601"2025-03-15"— Date only1710500400000— Unix timestamp in milliseconds (epoch_millis)
Common Mapping Mistakes
| Mistake | Impact | Fix |
|---|---|---|
| Not defining mappings | ES guesses wrong types | Always create explicit mappings |
Using text for status codes |
Can’t filter or aggregate properly | Use integer or keyword |
Forgetting keyword for IPs |
Can’t do IP range queries | Use ip type |
| Changing field types | Elasticsearch rejects the change | Reindex to a new index |
| Too many fields | “Mapping explosion” — performance issues | Set index.mapping.total_fields.limit |
Quick Reference
Concept Key Point Mapping Schema definition for an Elasticsearch index textFull-text search (analyzed into tokens) keywordExact match, filtering, sorting, aggregations dateDate/time values with configurable formats ipIP addresses with range query support Dynamic mapping ES guesses types (often wrong for numbers) Multi-field One field indexed as both textandkeywordCan’t change types Must reindex to fix mapping mistakes View mapping GET /my-index/_mapping
Next Step
You understand how data is structured. Now let’s learn about shards and replicas — how Elasticsearch distributes and protects your data.