InfluxDB’s query performance hinges on how you structure your data, and the distinction between tags and fields is the single most impactful decision you’ll make.

Let’s see this in action. Imagine we’re collecting sensor data.

// Example InfluxDB write for sensor data
client.WriteAPI.WritePoint(
	context.Background(),
	write.NewPoint(
		"sensor_data",
		map[string]string{ // These are TAGS
			"location": "warehouse_1",
			"sensor_id": "temp_sensor_001",
			"unit": "celsius",
		},
		map[string]interface{}{ // These are FIELDS
			"temperature": 22.5,
			"humidity": 45.2,
		},
		time.Now(),
	),
)

In this snippet, location, sensor_id, and unit are tags. temperature and humidity are fields.

The core problem InfluxDB solves is ingesting and querying massive amounts of time-series data efficiently. To do this, it uses a columnar storage format. When you query data, InfluxDB needs to quickly find the relevant data points. This is where tags and fields diverge in their behavior.

Tags are indexed. Think of them like the WHERE clause of a SQL query, but much more optimized. When you filter by a tag (e.g., WHERE location = 'warehouse_1'), InfluxDB can use its index to pinpoint exactly which data blocks contain that tag value. This is incredibly fast. Tags are stored separately from the actual measurement values and are designed for equality filtering, grouping, and ordering. They are typically used for metadata that describes the data point itself.

Fields, on the other hand, are not indexed in the same way. They are the actual values you’re interested in measuring. When you query a field (e.g., SELECT temperature FROM sensor_data), InfluxDB has to scan the relevant data blocks (which it first identified using tags) and extract the temperature values. This is why you want to minimize the number of fields you query directly, especially if you don’t filter by tags first. Fields are best suited for numerical or string data that you’ll aggregate, perform calculations on, or store as raw values.

The fundamental mental model to build is: tags for identifying and filtering, fields for measuring and aggregating.

If you have a high-cardinality tag (meaning it has many unique values, like a user ID in a web analytics scenario), it can consume a lot of memory and disk space because each unique tag value needs to be indexed. Conversely, if you have too many fields, even with good tag filtering, the scan to extract those field values can become a bottleneck.

Consider a scenario where you are tracking system metrics. A common mistake is to tag every single metric name:

// BAD: Tagging metric names
client.WriteAPI.WritePoint(
	context.Background(),
	write.NewPoint(
		"system_metrics",
		map[string]string{
			"metric_name": "cpu_usage", // BAD!
			"host": "server_01",
		},
		map[string]interface{}{
			"value": 85.5,
		},
		time.Now(),
	),
)

Here, metric_name is a tag, and if you have hundreds of different metric names (cpu_usage, memory_free, disk_io_wait, etc.), this creates a very high-cardinality tag. InfluxDB will build an index for every single one of those metric names. A better approach is to make metric_name a measurement or a field:

// GOOD: Using measurements for different metric types
client.WriteAPI.WritePoint(
	context.Background(),
	write.NewPoint(
		"cpu_usage", // Measurement
		map[string]string{
			"host": "server_01",
		},
		map[string]interface{}{
			"value": 85.5,
		},
		time.Now(),
	),
)

// GOOD: Using fields when multiple values belong to the same logical group
client.WriteAPI.WritePoint(
	context.Background(),
	write.NewPoint(
		"system_metrics",
		map[string]string{
			"host": "server_01",
		},
		map[string]interface{}{
			"cpu_usage": 85.5, // Field
			"memory_free_gb": 16.2, // Field
		},
		time.Now(),
	),
)

The second "GOOD" example is often preferable if you frequently query multiple metrics from the same host together. If you only ever query one metric type at a time, the first "GOOD" example (using separate measurements) might be better. The key is to analyze your typical query patterns.

When designing your schema, aim for a small number of low-cardinality tags that identify broad categories or locations (like host, region, datacenter, device_id). Put your actual measurements into fields. If you have a small, fixed set of related measurements, group them into a single measurement with multiple fields. If you have many different types of measurements that are rarely queried together, consider using separate measurements.

The most surprising thing about InfluxDB’s tag and field system is how much performance can shift based on whether you are filtering by tags before you select fields, versus trying to filter directly on field values or scanning many fields without tag filters. The storage engine is optimized for tag lookups and then scanning the minimal set of blocks for fields. Trying to reverse this flow, or having massive numbers of unique tag values, can drastically slow down queries that would otherwise be instantaneous.

The next logical step after optimizing your schema for query performance is understanding how to leverage InfluxDB’s continuous queries for pre-aggregating data.

Want structured learning?

Take the full Influxdb course →