Redis hashes

Introduction to Redis hashes

Hash command summary (view reference, 33 commands)

Redis hashes are record types structured as collections of field-value pairs. You can use hashes to represent basic objects and to store groupings of counters, among other things.

Foundational: Set and retrieve hash fields using HSET and HGET (overwrites existing field values)
HSET bike:1 model Deimos brand Ergonom type 'Enduro bikes' price 4972 HGET bike:1 model HGET bike:1 price HGETALL bike:1
res1 = r.hset(
    "bike:1",
    mapping={
        "model": "Deimos",
        "brand": "Ergonom",
        "type": "Enduro bikes",
        "price": 4972,
    },
)
print(res1)
# >>> 4

res2 = r.hget("bike:1", "model")
print(res2)
# >>> 'Deimos'

res3 = r.hget("bike:1", "price")
print(res3)
# >>> '4972'

res4 = r.hgetall("bike:1")
print(res4)
# >>> {'model': 'Deimos', 'brand': 'Ergonom', 'type': 'Enduro bikes', 'price': '4972'}

const res1 = await client.hSet(
  'bike:1',
  {
    'model': 'Deimos',
    'brand': 'Ergonom',
    'type': 'Enduro bikes',
    'price': 4972,
  }
)
console.log(res1) // 4

const res2 = await client.hGet('bike:1', 'model')
console.log(res2)  // 'Deimos'

const res3 = await client.hGet('bike:1', 'price')
console.log(res3)  // '4972'

const res4 = await client.hGetAll('bike:1')
console.log(res4)  
/*
{
  brand: 'Ergonom',
  model: 'Deimos',
  price: '4972',
  type: 'Enduro bikes'
}
*/
      Map<String, String> bike1 = new HashMap<>();
      bike1.put("model", "Deimos");
      bike1.put("brand", "Ergonom");
      bike1.put("type", "Enduro bikes");
      bike1.put("price", "4972");

      Long res1 = jedis.hset("bike:1", bike1);
      System.out.println(res1); // 4

      String res2 = jedis.hget("bike:1", "model");
      System.out.println(res2); // Deimos

      String res3 = jedis.hget("bike:1", "price");
      System.out.println(res3); // 4972

      Map<String, String> res4 = jedis.hgetAll("bike:1");
      System.out.println(res4); // {type=Enduro bikes, brand=Ergonom, price=4972, model=Deimos}
            Map<String, String> bike1 = new HashMap<>();
            bike1.put("model", "Deimos");
            bike1.put("brand", "Ergonom");
            bike1.put("type", "Enduro bikes");
            bike1.put("price", "4972");

            CompletableFuture<Void> setGetAll = asyncCommands.hset("bike:1", bike1).thenCompose(res1 -> {
                System.out.println(res1); // >>> 4
                return asyncCommands.hget("bike:1", "model");
            }).thenCompose(res2 -> {
                System.out.println(res2); // >>> Deimos
                return asyncCommands.hget("bike:1", "price");
            }).thenCompose(res3 -> {
                System.out.println(res3); // >>> 4972
                return asyncCommands.hgetall("bike:1");
            })
                    .thenAccept(System.out::println)
                    // >>> {type=Enduro bikes, brand=Ergonom, price=4972, model=Deimos}
                    .toCompletableFuture();
            Map<String, String> bike1 = new HashMap<>();
            bike1.put("model", "Deimos");
            bike1.put("brand", "Ergonom");
            bike1.put("type", "Enduro bikes");
            bike1.put("price", "4972");

            Mono<Long> setGetAll = reactiveCommands.hset("bike:1", bike1).doOnNext(result -> {
                System.out.println(result); // >>> 4
            });

            setGetAll.block();

            Mono<String> getModel = reactiveCommands.hget("bike:1", "model").doOnNext(result -> {
                System.out.println(result); // >>> Deimos
            });

            Mono<String> getPrice = reactiveCommands.hget("bike:1", "price").doOnNext(result -> {
                System.out.println(result); // >>> 4972
            });

            Mono<List<KeyValue<String, String>>> getAll = reactiveCommands.hgetall("bike:1").collectList().doOnNext(result -> {
                System.out.println(result);
                // >>> [KeyValue[type, Enduro bikes], KeyValue[brand, Ergonom],
                // KeyValue[price, 4972], KeyValue[model, Deimos]]
            });
	hashFields := []string{
		"model", "Deimos",
		"brand", "Ergonom",
		"type", "Enduro bikes",
		"price", "4972",
	}

	res1, err := rdb.HSet(ctx, "bike:1", hashFields).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res1) // >>> 4

	res2, err := rdb.HGet(ctx, "bike:1", "model").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res2) // >>> Deimos

	res3, err := rdb.HGet(ctx, "bike:1", "price").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res3) // >>> 4972

	cmdReturn := rdb.HGetAll(ctx, "bike:1")
	res4, err := cmdReturn.Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res4)
	// >>> map[brand:Ergonom model:Deimos price:4972 type:Enduro bikes]

	type BikeInfo struct {
		Model string `redis:"model"`
		Brand string `redis:"brand"`
		Type  string `redis:"type"`
		Price int    `redis:"price"`
	}

	var res4a BikeInfo

	if err := cmdReturn.Scan(&res4a); err != nil {
		panic(err)
	}

	fmt.Printf("Model: %v, Brand: %v, Type: %v, Price: $%v\n",
		res4a.Model, res4a.Brand, res4a.Type, res4a.Price)
	// >>> Model: Deimos, Brand: Ergonom, Type: Enduro bikes, Price: $4972
        db.HashSet("bike:1", [
            new("model", "Deimos"),
            new("brand", "Ergonom"),
            new("type", "Enduro bikes"),
            new("price", 4972)
        ]);

        Console.WriteLine("Hash Created");
        // Hash Created

        var model = db.HashGet("bike:1", "model");
        Console.WriteLine($"Model: {model}");
        // Model: Deimos

        var price = db.HashGet("bike:1", "price");
        Console.WriteLine($"Price: {price}");
        // Price: 4972

        var bike = db.HashGetAll("bike:1");
        Console.WriteLine("bike:1");
        Console.WriteLine(string.Join("\n", bike.Select(b => $"{b.Name}: {b.Value}")));
        // Bike:1:
        // model: Deimos
        // brand: Ergonom
        // type: Enduro bikes
        // price: 4972
        $res1 = $r->hmset('bike:1', [
            'model' => 'Deimos',
            'brand' => 'Ergonom',
            'type' => 'Enduro bikes',
            'price' => 4972,
        ]);

        echo $res1 . PHP_EOL;
        // >>> 4

        $res2 = $r->hget('bike:1', 'model');
        echo $res2 . PHP_EOL;
        // >>> Deimos

        $res3 = $r->hget('bike:1', 'price');
        echo $res3 . PHP_EOL;
        // >>> 4972

        $res4 = $r->hgetall('bike:1');
        echo json_encode($res3) . PHP_EOL;
        // >>> {"name":"Deimos","brand":"Ergonom","type":"Enduro bikes","price":"4972"}
res1 = r.hset('bike:1', {
  'model' => 'Deimos',
  'brand' => 'Ergonom',
  'type' => 'Enduro bikes',
  'price' => 4972
})
puts res1 # 4

res2 = r.hget('bike:1', 'model')
puts res2 # Deimos

res3 = r.hget('bike:1', 'price')
puts res3 # 4972

res4 = r.hgetall('bike:1')
puts res4.inspect
# {"model"=>"Deimos", "brand"=>"Ergonom", "type"=>"Enduro bikes", "price"=>"4972"}
        let hash_fields = [
            ("model", "Deimos"),
            ("brand", "Ergonom"),
            ("type", "Enduro bikes"),
            ("price", "4972"),
        ];

        if let Ok(res) = r.hset_multiple("bike:1", &hash_fields) {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.hget("bike:1", "model") {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> Deimos
            },
            Err(e) => {
                println!("Error getting bike:1 model: {e}");
                return;
            }
        };

        match r.hget("bike:1", "price") {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> 4972
            },
            Err(e) => {
                println!("Error getting bike:1 price: {e}");
                return;
            }
        };

        match r.hgetall("bike:1") {
            Ok(res) => {
                let res: Vec<(String, String)> = res;
                println!("{res:?}");
                // >>> [("model", "Deimos"), ("brand", "Ergonom"), ("type", "Enduro bikes"), ("price", "4972")]
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        };
        let hash_fields = [
            ("model", "Deimos"),
            ("brand", "Ergonom"),
            ("type", "Enduro bikes"),
            ("price", "4972"),
        ];

        if let Ok(res) = r.hset_multiple("bike:1", &hash_fields).await {
            let res: String = res;
            println!("{res}");    // >>> OK
        }

        match r.hget("bike:1", "model").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> Deimos
            },
            Err(e) => {
                println!("Error getting bike:1 model: {e}");
                return;
            }
        };

        match r.hget("bike:1", "price").await {
            Ok(res) => {
                let res: String = res;
                println!("{res}");   // >>> 4972
            },
            Err(e) => {
                println!("Error getting bike:1 price: {e}");
                return;
            }
        };

        match r.hgetall("bike:1").await {
            Ok(res) => {
                let res: Vec<(String, String)> = res;
                println!("{res:?}");
                // >>> [("model", "Deimos"), ("brand", "Ergonom"), ("type", "Enduro bikes"), ("price", "4972")]
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        };

While hashes are handy to represent objects, actually the number of fields you can put inside a hash has no practical limits (other than available memory), so you can use hashes in many different ways inside your application.

The command HSET sets multiple fields of the hash, while HGET retrieves a single field. HMGET is similar to HGET but returns an array of values:

Retrieve multiple field values from a hash using HMGET when you need to reduce round trips to the server
DEL bike:1 HSET bike:1 model Deimos brand Ergonom type 'Enduro bikes' price 4972 HMGET bike:1 model price no-such-field
# Recreate the bike:1 hash so this example runs on its own.
r.delete("bike:1")
r.hset(
    "bike:1",
    mapping={
        "model": "Deimos",
        "brand": "Ergonom",
        "type": "Enduro bikes",
        "price": 4972,
    },
)

res5 = r.hmget("bike:1", ["model", "price"])
print(res5)
# >>> ['Deimos', '4972']
// Recreate the bike:1 hash so this example runs on its own.
await client.del('bike:1')
await client.hSet(
  'bike:1',
  {
    'model': 'Deimos',
    'brand': 'Ergonom',
    'type': 'Enduro bikes',
    'price': 4972,
  }
)

const res5 = await client.hmGet('bike:1', ['model', 'price'])
console.log(res5)  // ['Deimos', '4972']
      // Recreate the bike:1 hash so this example runs on its own.
      jedis.del("bike:1");
      jedis.hset("bike:1", bike1);

      List<String> res5 = jedis.hmget("bike:1", "model", "price");
      System.out.println(res5); // [Deimos, 4972]
            // Recreate the bike:1 hash so this example runs on its own.
            CompletableFuture<Void> hmGet = setGetAll
                    .thenCompose(res4 -> asyncCommands.del("bike:1"))
                    .thenCompose(delRes -> asyncCommands.hset("bike:1", bike1))
                    .thenCompose(hsetRes -> asyncCommands.hmget("bike:1", "model", "price"))
                    .thenAccept(System.out::println)
                    // [KeyValue[model, Deimos], KeyValue[price, 4972]]
                    .toCompletableFuture();
            // Recreate the bike:1 hash so this example runs on its own.
            Mono<List<KeyValue<String, String>>> hmGet = reactiveCommands.del("bike:1")
                    .then(reactiveCommands.hset("bike:1", bike1))
                    .then(reactiveCommands.hmget("bike:1", "model", "price").collectList())
                    .doOnNext(result -> {
                        System.out.println(result);
                        // >>> [KeyValue[model, Deimos], KeyValue[price, 4972]]
                    });
	// Recreate the bike:1 hash so this example runs on its own.
	rdb.Del(ctx, "bike:1")
	hashFields := []string{
		"model", "Deimos",
		"brand", "Ergonom",
		"type", "Enduro bikes",
		"price", "4972",
	}
	rdb.HSet(ctx, "bike:1", hashFields)

	cmdReturn := rdb.HMGet(ctx, "bike:1", "model", "price")
	res5, err := cmdReturn.Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res5) // >>> [Deimos 4972]

	type BikeInfo struct {
		Model string `redis:"model"`
		Brand string `redis:"-"`
		Type  string `redis:"-"`
		Price int    `redis:"price"`
	}

	var res5a BikeInfo

	if err := cmdReturn.Scan(&res5a); err != nil {
		panic(err)
	}

	fmt.Printf("Model: %v, Price: $%v\n", res5a.Model, res5a.Price)
	// >>> Model: Deimos, Price: $4972
        // Recreate the bike:1 hash so this example runs on its own.
        db.KeyDelete("bike:1");
        db.HashSet("bike:1", [
            new("model", "Deimos"),
            new("brand", "Ergonom"),
            new("type", "Enduro bikes"),
            new("price", 4972)
        ]);

        var values = db.HashGet("bike:1", ["model", "price"]);
        Console.WriteLine(string.Join(" ", values));
        // Deimos 4972
        // Recreate the bike:1 hash so this example runs on its own.
        $r->del('bike:1');
        $r->hmset('bike:1', [
            'model' => 'Deimos',
            'brand' => 'Ergonom',
            'type' => 'Enduro bikes',
            'price' => 4972,
        ]);

        $res5 = $r->hmget('bike:1', ['model', 'price']);
        echo json_encode($res5) . PHP_EOL;
        // >>> ["Deimos","4972"]
# Recreate the bike:1 hash so this example runs on its own.
r.del('bike:1')
r.hset('bike:1', {
  'model' => 'Deimos',
  'brand' => 'Ergonom',
  'type' => 'Enduro bikes',
  'price' => 4972
})

res5 = r.hmget('bike:1', 'model', 'price', 'no-such-field')
puts res5.inspect # ["Deimos", "4972", nil]
        // Recreate the bike:1 hash so this example runs on its own.
        let _: () = r.del("bike:1").expect("Failed to del");
        let _: () = r.hset_multiple(
            "bike:1",
            &[("model", "Deimos"), ("brand", "Ergonom"), ("type", "Enduro bikes"), ("price", "4972")],
        ).expect("Failed to hset");

        match r.hmget("bike:1", &["model", "price"]) {
            Ok(res) => {
                let res: Vec<String> = res;
                println!("{res:?}");   // >>> ["Deimos", "4972"]
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        };
        // Recreate the bike:1 hash so this example runs on its own.
        let _: () = r.del("bike:1").await.expect("Failed to del");
        let _: () = r.hset_multiple(
            "bike:1",
            &[("model", "Deimos"), ("brand", "Ergonom"), ("type", "Enduro bikes"), ("price", "4972")],
        ).await.expect("Failed to hset");

        match r.hmget("bike:1", &["model", "price"]).await {
            Ok(res) => {
                let res: Vec<String> = res;
                println!("{res:?}");   // >>> ["Deimos", "4972"]
            },
            Err(e) => {
                println!("Error getting bike:1: {e}");
                return;
            }
        };

There are commands that are able to perform operations on individual fields as well, like HINCRBY:

Increment hash field values for counters using HINCRBY (creates field if missing, initializes to 0)
DEL bike:1 HSET bike:1 model Deimos brand Ergonom type 'Enduro bikes' price 4972 HINCRBY bike:1 price 100 HINCRBY bike:1 price -100
# Recreate the bike:1 hash so this example runs on its own.
r.delete("bike:1")
r.hset(
    "bike:1",
    mapping={
        "model": "Deimos",
        "brand": "Ergonom",
        "type": "Enduro bikes",
        "price": 4972,
    },
)

res6 = r.hincrby("bike:1", "price", 100)
print(res6)
# >>> 5072
res7 = r.hincrby("bike:1", "price", -100)
print(res7)
# >>> 4972
// Recreate the bike:1 hash so this example runs on its own.
await client.del('bike:1')
await client.hSet(
  'bike:1',
  {
    'model': 'Deimos',
    'brand': 'Ergonom',
    'type': 'Enduro bikes',
    'price': 4972,
  }
)

const res6 = await client.hIncrBy('bike:1', 'price', 100)
console.log(res6)  // 5072
const res7 = await client.hIncrBy('bike:1', 'price', -100)
console.log(res7)  // 4972
      // Recreate the bike:1 hash so this example runs on its own.
      jedis.del("bike:1");
      jedis.hset("bike:1", bike1);

      Long res6 = jedis.hincrBy("bike:1", "price", 100);
      System.out.println(res6); // 5072
      Long res7 = jedis.hincrBy("bike:1", "price", -100);
      System.out.println(res7); // 4972
            // Recreate the bike:1 hash so this example runs on its own.
            CompletableFuture<Void> hIncrBy = hmGet
                    .thenCompose(r -> asyncCommands.del("bike:1"))
                    .thenCompose(delRes -> asyncCommands.hset("bike:1", bike1))
                    .thenCompose(hsetRes -> asyncCommands.hincrby("bike:1", "price", 100))
                    .thenCompose(res6 -> {
                        System.out.println(res6); // >>> 5072
                        return asyncCommands.hincrby("bike:1", "price", -100);
                    })
                    .thenAccept(System.out::println)
                    // >>> 4972
                    .toCompletableFuture();
            // Recreate the bike:1 hash so this example runs on its own.
            Mono<Void> hIncrBy = reactiveCommands.del("bike:1")
                    .then(reactiveCommands.hset("bike:1", bike1))
                    .then(reactiveCommands.hincrby("bike:1", "price", 100)).doOnNext(result -> {
                System.out.println(result); // >>> 5072
            }).flatMap(v -> reactiveCommands.hincrby("bike:1", "price", -100)).doOnNext(result -> {
                System.out.println(result); // >>> 4972
            }).then();
	// Recreate the bike:1 hash so this example runs on its own.
	rdb.Del(ctx, "bike:1")
	hashFields := []string{
		"model", "Deimos",
		"brand", "Ergonom",
		"type", "Enduro bikes",
		"price", "4972",
	}
	rdb.HSet(ctx, "bike:1", hashFields)

	res6, err := rdb.HIncrBy(ctx, "bike:1", "price", 100).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res6) // >>> 5072

	res7, err := rdb.HIncrBy(ctx, "bike:1", "price", -100).Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res7) // >>> 4972
        // Recreate the bike:1 hash so this example runs on its own.
        db.KeyDelete("bike:1");
        db.HashSet("bike:1", [
            new("model", "Deimos"),
            new("brand", "Ergonom"),
            new("type", "Enduro bikes"),
            new("price", 4972)
        ]);

        var newPrice = db.HashIncrement("bike:1", "price", 100);
        Console.WriteLine($"New price: {newPrice}");
        // New price: 5072

        newPrice = db.HashIncrement("bike:1", "price", -100);
        Console.WriteLine($"New price: {newPrice}");
        // New price: 4972
        // Recreate the bike:1 hash so this example runs on its own.
        $r->del('bike:1');
        $r->hmset('bike:1', [
            'model' => 'Deimos',
            'brand' => 'Ergonom',
            'type' => 'Enduro bikes',
            'price' => 4972,
        ]);

        $res6 = $r->hincrby('bike:1', 'price', 100);
        echo $res6 . PHP_EOL;
        // >>> 5072

        $res7 = $r->hincrby('bike:1', 'price', -100);
        echo $res7 . PHP_EOL;
        // >>> 4972
# Recreate the bike:1 hash so this example runs on its own.
r.del('bike:1')
r.hset('bike:1', {
  'model' => 'Deimos',
  'brand' => 'Ergonom',
  'type' => 'Enduro bikes',
  'price' => 4972
})

res6 = r.hincrby('bike:1', 'price', 100)
puts res6 # 5072

res7 = r.hincrby('bike:1', 'price', -100)
puts res7 # 4972
        // Recreate the bike:1 hash so this example runs on its own.
        let _: () = r.del("bike:1").expect("Failed to del");
        let _: () = r.hset_multiple(
            "bike:1",
            &[("model", "Deimos"), ("brand", "Ergonom"), ("type", "Enduro bikes"), ("price", "4972")],
        ).expect("Failed to hset");

        if let Ok(res) = r.hincr("bike:1", "price", 100) {
            let res: i32 = res;
            println!("{res}");    // >>> 5072
        }

        if let Ok(res) = r.hincr("bike:1", "price", -100) {
            let res: i32 = res;
            println!("{res}");    // >>> 4972
        }
        // Recreate the bike:1 hash so this example runs on its own.
        let _: () = r.del("bike:1").await.expect("Failed to del");
        let _: () = r.hset_multiple(
            "bike:1",
            &[("model", "Deimos"), ("brand", "Ergonom"), ("type", "Enduro bikes"), ("price", "4972")],
        ).await.expect("Failed to hset");

        if let Ok(res) = r.hincr("bike:1", "price", 100).await {
            let res: i32 = res;
            println!("{res}");    // >>> 5072
        }

        if let Ok(res) = r.hincr("bike:1", "price", -100).await {
            let res: i32 = res;
            println!("{res}");    // >>> 4972
        }

You can find the full list of hash commands in the documentation.

It is worth noting that small hashes (i.e., a few elements with small values) are encoded in special way in memory that make them very memory efficient.

Examples

  • Store counters for the number of times bike:1 has been ridden, has crashed, or has changed owners:
    Practical pattern: Combine HINCRBY and HMGET to track multiple counters when you need atomic updates across multiple fields
    HINCRBY bike:1:stats rides 1 HINCRBY bike:1:stats rides 1 HINCRBY bike:1:stats rides 1 HINCRBY bike:1:stats crashes 1 HINCRBY bike:1:stats owners 1 HGET bike:1:stats rides HMGET bike:1:stats owners crashes
    res11 = r.hincrby("bike:1:stats", "rides", 1)
    print(res11)
    # >>> 1
    res12 = r.hincrby("bike:1:stats", "rides", 1)
    print(res12)
    # >>> 2
    res13 = r.hincrby("bike:1:stats", "rides", 1)
    print(res13)
    # >>> 3
    res14 = r.hincrby("bike:1:stats", "crashes", 1)
    print(res14)
    # >>> 1
    res15 = r.hincrby("bike:1:stats", "owners", 1)
    print(res15)
    # >>> 1
    res16 = r.hget("bike:1:stats", "rides")
    print(res16)
    # >>> 3
    res17 = r.hmget("bike:1:stats", ["crashes", "owners"])
    print(res17)
    # >>> ['1', '1']
    
    import assert from 'assert';
    import { createClient } from 'redis';
    
    const client = createClient();
    await client.connect();
    const res1 = await client.hSet(
      'bike:1',
      {
        'model': 'Deimos',
        'brand': 'Ergonom',
        'type': 'Enduro bikes',
        'price': 4972,
      }
    )
    console.log(res1) // 4
    
    const res2 = await client.hGet('bike:1', 'model')
    console.log(res2)  // 'Deimos'
    
    const res3 = await client.hGet('bike:1', 'price')
    console.log(res3)  // '4972'
    
    const res4 = await client.hGetAll('bike:1')
    console.log(res4)  
    /*
    {
      brand: 'Ergonom',
      model: 'Deimos',
      price: '4972',
      type: 'Enduro bikes'
    }
    */
    
    
    // Recreate the bike:1 hash so this example runs on its own.
    await client.del('bike:1')
    await client.hSet(
      'bike:1',
      {
        'model': 'Deimos',
        'brand': 'Ergonom',
        'type': 'Enduro bikes',
        'price': 4972,
      }
    )
    
    const res5 = await client.hmGet('bike:1', ['model', 'price'])
    console.log(res5)  // ['Deimos', '4972']
    
    
    // Recreate the bike:1 hash so this example runs on its own.
    await client.del('bike:1')
    await client.hSet(
      'bike:1',
      {
        'model': 'Deimos',
        'brand': 'Ergonom',
        'type': 'Enduro bikes',
        'price': 4972,
      }
    )
    
    const res6 = await client.hIncrBy('bike:1', 'price', 100)
    console.log(res6)  // 5072
    const res7 = await client.hIncrBy('bike:1', 'price', -100)
    console.log(res7)  // 4972
    
    
    const res11 = await client.hIncrBy('bike:1:stats', 'rides', 1)
    console.log(res11)  // 1
    const res12 = await client.hIncrBy('bike:1:stats', 'rides', 1)
    console.log(res12)  // 2
    const res13 = await client.hIncrBy('bike:1:stats', 'rides', 1)
    console.log(res13)  // 3
    const res14 = await client.hIncrBy('bike:1:stats', 'crashes', 1)
    console.log(res14)  // 1
    const res15 = await client.hIncrBy('bike:1:stats', 'owners', 1)
    console.log(res15)  // 1
    const res16 = await client.hGet('bike:1:stats', 'rides')
    console.log(res16)  // 3
    const res17 = await client.hmGet('bike:1:stats', ['crashes', 'owners'])
    console.log(res17)  // ['1', '1']
    
    
    await client.del('sensor:sensor1')
    await client.hSet('sensor:sensor1', { 'air_quality': 256, 'battery_level': 89 })
    
    // Set a TTL of 60 seconds on two fields of the hash.
    const res18 = await client.hExpire('sensor:sensor1', ['air_quality', 'battery_level'], 60)
    console.log(res18)  // [1, 1]
    
    // Retrieve the remaining TTL for those fields.
    const res19 = await client.hTTL('sensor:sensor1', ['air_quality', 'battery_level'])
    console.log(res19)  // [60, 60] (or close to 60)
    
    
    await client.del('sensor:sensor1')
    await client.hSet('sensor:sensor1', { 'air_quality': 256, 'battery_level': 89 })
    
    // Set the TTL of the 'air_quality' field in milliseconds.
    const res20 = await client.hpExpire('sensor:sensor1', ['air_quality'], 60000)
    console.log(res20)  // [1]
    
    // Retrieve the remaining TTL in milliseconds.
    const res21 = await client.hpTTL('sensor:sensor1', ['air_quality'])
    console.log(res21)  // [59994] (your actual value may vary)
    
    
    await client.del('sensor:sensor1')
    await client.hSet('sensor:sensor1', { 'air_quality': 256, 'battery_level': 89 })
    
    // Set the expiration of 'air_quality' to a Unix time 24 hours from now.
    const expireAt = Math.floor(Date.now() / 1000) + 24 * 60 * 60
    const res22 = await client.hExpireAt('sensor:sensor1', ['air_quality'], expireAt)
    console.log(res22)  // [1]
    
    // Retrieve the expiration time as a Unix timestamp in seconds.
    const res23 = await client.hExpireTime('sensor:sensor1', ['air_quality'])
    console.log(res23)  // [1717668041] (your actual value will vary)
    
    
          Long res8 = jedis.hincrBy("bike:1:stats", "rides", 1);
          System.out.println(res8); // 1
          Long res9 = jedis.hincrBy("bike:1:stats", "rides", 1);
          System.out.println(res9); // 2
          Long res10 = jedis.hincrBy("bike:1:stats", "rides", 1);
          System.out.println(res10); // 3
          Long res11 = jedis.hincrBy("bike:1:stats", "crashes", 1);
          System.out.println(res11); // 1
          Long res12 = jedis.hincrBy("bike:1:stats", "owners", 1);
          System.out.println(res12); // 1
          String res13 = jedis.hget("bike:1:stats", "rides");
          System.out.println(res13); // 3
          List<String> res14 = jedis.hmget("bike:1:stats", "crashes", "owners");
          System.out.println(res14); // [1, 1]
    
                CompletableFuture<Void> incrByGetMget = asyncCommands.hincrby("bike:1:stats", "rides", 1).thenCompose(res7 -> {
                    System.out.println(res7); // >>> 1
                    return asyncCommands.hincrby("bike:1:stats", "rides", 1);
                }).thenCompose(res8 -> {
                    System.out.println(res8); // >>> 2
                    return asyncCommands.hincrby("bike:1:stats", "rides", 1);
                }).thenCompose(res9 -> {
                    System.out.println(res9); // >>> 3
                    return asyncCommands.hincrby("bike:1:stats", "crashes", 1);
                }).thenCompose(res10 -> {
                    System.out.println(res10); // >>> 1
                    return asyncCommands.hincrby("bike:1:stats", "owners", 1);
                }).thenCompose(res11 -> {
                    System.out.println(res11); // >>> 1
                    return asyncCommands.hget("bike:1:stats", "rides");
                }).thenCompose(res12 -> {
                    System.out.println(res12); // >>> 3
                    return asyncCommands.hmget("bike:1:stats", "crashes", "owners");
                })
                        .thenAccept(System.out::println)
                        // >>> [KeyValue[crashes, 1], KeyValue[owners, 1]]
                        .toCompletableFuture();
    
                Mono<Void> incrByGetMget = reactiveCommands.hincrby("bike:1:stats", "rides", 1).doOnNext(result -> {
                    System.out.println(result); // >>> 1
                }).flatMap(v -> reactiveCommands.hincrby("bike:1:stats", "rides", 1)).doOnNext(result -> {
                    System.out.println(result); // >>> 2
                }).flatMap(v -> reactiveCommands.hincrby("bike:1:stats", "rides", 1)).doOnNext(result -> {
                    System.out.println(result); // >>> 3
                }).flatMap(v -> reactiveCommands.hincrby("bike:1:stats", "crashes", 1)).doOnNext(result -> {
                    System.out.println(result); // >>> 1
                }).flatMap(v -> reactiveCommands.hincrby("bike:1:stats", "owners", 1)).doOnNext(result -> {
                    System.out.println(result); // >>> 1
                }).then();
    
                incrByGetMget.block();
    
                Mono<String> getRides = reactiveCommands.hget("bike:1:stats", "rides").doOnNext(result -> {
                    System.out.println(result); // >>> 3
                });
    
                Mono<List<KeyValue<String, String>>> getCrashesOwners = reactiveCommands.hmget("bike:1:stats", "crashes", "owners")
                        .collectList().doOnNext(result -> {
                            System.out.println(result);
                            // >>> [KeyValue[crashes, 1], KeyValue[owners, 1]]
                        });
    
    	res8, err := rdb.HIncrBy(ctx, "bike:1:stats", "rides", 1).Result()
    
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(res8) // >>> 1
    
    	res9, err := rdb.HIncrBy(ctx, "bike:1:stats", "rides", 1).Result()
    
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(res9) // >>> 2
    
    	res10, err := rdb.HIncrBy(ctx, "bike:1:stats", "rides", 1).Result()
    
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(res10) // >>> 3
    
    	res11, err := rdb.HIncrBy(ctx, "bike:1:stats", "crashes", 1).Result()
    
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(res11) // >>> 1
    
    	res12, err := rdb.HIncrBy(ctx, "bike:1:stats", "owners", 1).Result()
    
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(res12) // >>> 1
    
    	res13, err := rdb.HGet(ctx, "bike:1:stats", "rides").Result()
    
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(res13) // >>> 3
    
    	res14, err := rdb.HMGet(ctx, "bike:1:stats", "crashes", "owners").Result()
    
    	if err != nil {
    		panic(err)
    	}
    
    	fmt.Println(res14) // >>> [1 1]
    
            var rides = db.HashIncrement("bike:1", "rides");
            Console.WriteLine($"Rides: {rides}");
            // Rides: 1
    
            rides = db.HashIncrement("bike:1", "rides");
            Console.WriteLine($"Rides: {rides}");
            // Rides: 2
    
            rides = db.HashIncrement("bike:1", "rides");
            Console.WriteLine($"Rides: {rides}");
            // Rides: 3
    
            var crashes = db.HashIncrement("bike:1", "crashes");
            Console.WriteLine($"Crashes: {crashes}");
            // Crashes: 1
    
            var owners = db.HashIncrement("bike:1", "owners");
            Console.WriteLine($"Owners: {owners}");
            // Owners: 1
    
            var stats = db.HashGet("bike:1", ["crashes", "owners"]);
            Console.WriteLine($"Bike stats: crashes={stats[0]}, owners={stats[1]}");
            // Bike stats: crashes=1, owners=1
    
            $res8 = $r->hincrby('bike:1:stats', 'rides', 1);
            echo $res8 . PHP_EOL;
            // >>> 1
    
            $res9 = $r->hincrby('bike:1:stats', 'rides', 1);
            echo $res9 . PHP_EOL;
            // >>> 2
    
            $res10 = $r->hincrby('bike:1:stats', 'rides', 1);
            echo $res10 . PHP_EOL;
            // >>> 3
    
            $res11 = $r->hincrby('bike:1:stats', 'crashes', 1);
            echo $res11 . PHP_EOL;
            // >>> 1
    
            $res12 = $r->hincrby('bike:1:stats', 'owners', 1);
            echo $res12 . PHP_EOL;
            // >>> 1
    
            $res13 = $r->hget('bike:1:stats', 'rides');
            echo $res13 . PHP_EOL;
            // >>> 3
    
            $res14 = $r->hmget('bike:1:stats', ['crashes', 'owners']);
            echo json_encode($res14) . PHP_EOL;
            // >>> ["1","1"]
    
    res8 = r.hincrby('bike:1:stats', 'rides', 1)
    puts res8 # 1
    
    res9 = r.hincrby('bike:1:stats', 'rides', 1)
    puts res9 # 2
    
    res10 = r.hincrby('bike:1:stats', 'rides', 1)
    puts res10 # 3
    
    res11 = r.hincrby('bike:1:stats', 'crashes', 1)
    puts res11 # 1
    
    res12 = r.hincrby('bike:1:stats', 'owners', 1)
    puts res12 # 1
    
    res13 = r.hget('bike:1:stats', 'rides')
    puts res13 # 3
    
    res14 = r.hmget('bike:1:stats', 'owners', 'crashes')
    puts res14.inspect # ["1", "1"]
    
            if let Ok(res) = r.hincr("bike:1:stats", "rides", 1) {
                let res: i32 = res;
                println!("{res}");    // >>> 1
            }
    
            if let Ok(res) = r.hincr("bike:1:stats", "rides", 1) {
                let res: i32 = res;
                println!("{res}");    // >>> 2
            }
    
            if let Ok(res) = r.hincr("bike:1:stats", "rides", 1) {
                let res: i32 = res;
                println!("{res}");    // >>> 3
            }
    
            if let Ok(res) = r.hincr("bike:1:stats", "crashes", 1) {
                let res: i32 = res;
                println!("{res}");    // >>> 1
            }
    
            if let Ok(res) = r.hincr("bike:1:stats", "owners", 1) {
                let res: i32 = res;
                println!("{res}");    // >>> 1
            }
    
            match r.hget("bike:1:stats", "rides") {
                Ok(res) => {
                    let res: i32 = res;
                    println!("{res}");   // >>> 3
                },
                Err(e) => {
                    println!("Error getting bike:1:stats rides: {e}");
                    return;
                }
            };
    
            match r.hmget("bike:1:stats", &["crashes", "owners"]) {
                Ok(res) => {
                    let res: Vec<i32> = res;
                    println!("{res:?}");   // >>> [1, 1]
                },
                Err(e) => {
                    println!("Error getting bike:1:stats crashes and owners: {e}");
                    return;
                }
            };
    
            if let Ok(res) = r.hincr("bike:1:stats", "rides", 1).await {
                let res: i32 = res;
                println!("{res}");    // >>> 1
            }
    
            if let Ok(res) = r.hincr("bike:1:stats", "rides", 1).await {
                let res: i32 = res;
                println!("{res}");    // >>> 2
            }
    
            if let Ok(res) = r.hincr("bike:1:stats", "rides", 1).await {
                let res: i32 = res;
                println!("{res}");    // >>> 3
            }
    
            if let Ok(res) = r.hincr("bike:1:stats", "crashes", 1).await {
                let res: i32 = res;
                println!("{res}");    // >>> 1
            }
    
            if let Ok(res) = r.hincr("bike:1:stats", "owners", 1).await {
                let res: i32 = res;
                println!("{res}");    // >>> 1
            }
    
            match r.hget("bike:1:stats", "rides").await {
                Ok(res) => {
                    let res: i32 = res;
                    println!("{res}");   // >>> 3
                },
                Err(e) => {
                    println!("Error getting bike:1:stats rides: {e}");
                    return;
                }
            };
    
            match r.hmget("bike:1:stats", &["crashes", "owners"]).await {
                Ok(res) => {
                    let res: Vec<i32> = res;
                    println!("{res:?}");   // >>> [1, 1]
                },
                Err(e) => {
                    println!("Error getting bike:1:stats crashes and owners: {e}");
                    return;
                }
            };
    

Field expiration

Redis 7.4 introduced the ability to specify an expiration time or a time-to-live (TTL) value for individual hash fields. This capability is comparable to key expiration and includes a number of similar commands.

Use the following commands to set either an exact expiration time or a TTL value for specific fields:

  • HEXPIRE: set the remaining TTL in seconds.
  • HPEXPIRE: set the remaining TTL in milliseconds.
  • HEXPIREAT: set the expiration time to a timestamp1 specified in seconds.
  • HPEXPIREAT: set the expiration time to a timestamp specified in milliseconds.

Use the following commands to retrieve either the exact time when or the remaining TTL until specific fields will expire:

  • HEXPIRETIME: get the expiration time as a timestamp in seconds.
  • HPEXPIRETIME: get the expiration time as a timestamp in milliseconds.
  • HTTL: get the remaining TTL in seconds.
  • HPTTL: get the remaining TTL in milliseconds.

Use the following command to remove the expiration of specific fields:

Redis 8.0 introduced the following commands:

  • HGETEX: Get the value of one or more fields of a given hash key and optionally set their expiration time or time-to-live (TTL).
  • HSETEX: Set the value of one or more fields of a given hash key and optionally set their expiration time or time-to-live (TTL).

Common field expiration use cases

  1. Event Tracking: Use a hash key to store events from the last hour. Set each event's TTL to one hour. Use HLEN to count events from the past hour.

  2. Fraud Detection: Create a hash with hourly counters for events. Set each field's TTL to 48 hours. Query the hash to get the number of events per hour for the last 48 hours.

  3. Customer Session Management: Store customer data in hash keys. Create a new hash key for each session and add a session field to the customer’s hash key. Expire both the session key and the session field in the customer’s hash key automatically when the session expires.

  4. Active Session Tracking: Store all active sessions in a hash key. Set each session's TTL to expire automatically after inactivity. Use HLEN to count active sessions.

Field expiration examples

Hash field expiration is supported by the official client libraries. The examples below demonstrate the field expiration commands using a hash that stores sensor data with the following structure:

Field Value
air_quality 256
battery_level 89

Because the fields expire, each example recreates the sensor:sensor1 hash first so that it runs on its own.

Set a TTL of 60 seconds for two fields of a hash and then retrieve the remaining TTL for those fields:

Field expiration: Set a TTL in seconds on individual hash fields using HEXPIRE, then read the remaining TTL with HTTL
DEL sensor:sensor1 HSET sensor:sensor1 air_quality 256 battery_level 89 HEXPIRE sensor:sensor1 60 FIELDS 2 air_quality battery_level HTTL sensor:sensor1 FIELDS 2 air_quality battery_level
r.delete("sensor:sensor1")
r.hset("sensor:sensor1", mapping={"air_quality": 256, "battery_level": 89})

# Set a TTL of 60 seconds on two fields of the hash.
res18 = r.hexpire("sensor:sensor1", 60, "air_quality", "battery_level")
print(res18)
# >>> [1, 1]

# Retrieve the remaining TTL for those fields.
res19 = r.httl("sensor:sensor1", "air_quality", "battery_level")
print(res19)
# >>> [60, 60]
# (your actual values may be slightly lower)
await client.del('sensor:sensor1')
await client.hSet('sensor:sensor1', { 'air_quality': 256, 'battery_level': 89 })

// Set a TTL of 60 seconds on two fields of the hash.
const res18 = await client.hExpire('sensor:sensor1', ['air_quality', 'battery_level'], 60)
console.log(res18)  // [1, 1]

// Retrieve the remaining TTL for those fields.
const res19 = await client.hTTL('sensor:sensor1', ['air_quality', 'battery_level'])
console.log(res19)  // [60, 60] (or close to 60)
      jedis.del("sensor:sensor1");
      Map<String, String> sensor1 = new HashMap<>();
      sensor1.put("air_quality", "256");
      sensor1.put("battery_level", "89");
      jedis.hset("sensor:sensor1", sensor1);

      // Set a TTL of 60 seconds on two fields of the hash.
      List<Long> res15 = jedis.hexpire("sensor:sensor1", 60, "air_quality", "battery_level");
      System.out.println(res15); // [1, 1]

      // Retrieve the remaining TTL for those fields.
      List<Long> res16 = jedis.httl("sensor:sensor1", "air_quality", "battery_level");
      System.out.println(res16.size()); // 2
            Map<String, String> sensor1 = new HashMap<>();
            sensor1.put("air_quality", "256");
            sensor1.put("battery_level", "89");

            CompletableFuture<Void> hExpire = asyncCommands.del("sensor:sensor1")
                    .thenCompose(delRes -> asyncCommands.hset("sensor:sensor1", sensor1))
                    // Set a TTL of 60 seconds on two fields of the hash.
                    .thenCompose(hsetRes -> asyncCommands.hexpire("sensor:sensor1", 60, "air_quality", "battery_level"))
                    .thenCompose(res15 -> {
                        System.out.println(res15); // >>> [1, 1]
                        // Retrieve the remaining TTL for those fields.
                        return asyncCommands.httl("sensor:sensor1", "air_quality", "battery_level");
                    })
                    .thenAccept(res16 -> System.out.println(res16.size()))
                    // >>> 2
                    .toCompletableFuture();
            Map<String, String> sensor1 = new HashMap<>();
            sensor1.put("air_quality", "256");
            sensor1.put("battery_level", "89");

            // Set a TTL of 60 seconds on two fields of the hash.
            Mono<List<Long>> hExpire = reactiveCommands.del("sensor:sensor1")
                    .then(reactiveCommands.hset("sensor:sensor1", sensor1))
                    .then(reactiveCommands.hexpire("sensor:sensor1", 60, "air_quality", "battery_level").collectList())
                    .doOnNext(result -> {
                        System.out.println(result);
                        // >>> [1, 1]
                    });

            hExpire.block();

            // Retrieve the remaining TTL for those fields.
            Mono<List<Long>> hTtl = reactiveCommands.httl("sensor:sensor1", "air_quality", "battery_level")
                    .collectList().doOnNext(result -> {
                        System.out.println(result.size());
                        // >>> 2
                    });

            hTtl.block();
	rdb.Del(ctx, "sensor:sensor1")
	rdb.HSet(ctx, "sensor:sensor1", "air_quality", 256, "battery_level", 89)

	// Set a TTL of 60 seconds on two fields of the hash.
	res18, err := rdb.HExpire(ctx, "sensor:sensor1", 60*time.Second,
		"air_quality", "battery_level").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res18) // >>> [1 1]

	// Retrieve the remaining TTL for those fields (returns one value per field).
	res19, err := rdb.HTTL(ctx, "sensor:sensor1", "air_quality", "battery_level").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(len(res19)) // >>> 2
        db.KeyDelete("sensor:sensor1");
        db.HashSet("sensor:sensor1", [
            new("air_quality", 256),
            new("battery_level", 89)
        ]);

        // Set a TTL of 60 seconds on two fields of the hash.
        ExpireResult[] hexpireResult = db.HashFieldExpire(
            "sensor:sensor1",
            new RedisValue[] { "air_quality", "battery_level" },
            TimeSpan.FromSeconds(60)
        );
        Console.WriteLine(string.Join(", ", hexpireResult));
        // Success, Success

        // Retrieve the remaining TTL for those fields.
        long[] httlResult = db.HashFieldGetTimeToLive(
            "sensor:sensor1",
            new RedisValue[] { "air_quality", "battery_level" }
        );
        Console.WriteLine(httlResult.Length);
        // 2
        $r->del('sensor:sensor1');
        $r->hmset('sensor:sensor1', [
            'air_quality' => 256,
            'battery_level' => 89,
        ]);

        // Set a TTL of 60 seconds on two fields of the hash.
        $res15 = $r->hexpire('sensor:sensor1', 60, ['air_quality', 'battery_level']);
        echo json_encode($res15) . PHP_EOL;
        // >>> [1,1]

        // Retrieve the remaining TTL for those fields.
        $res16 = $r->httl('sensor:sensor1', ['air_quality', 'battery_level']);
        echo count($res16) . PHP_EOL;
        // >>> 2
        let _: () = r.del("sensor:sensor1").expect("Failed to del");
        let _: () = r.hset_multiple(
            "sensor:sensor1",
            &[("air_quality", "256"), ("battery_level", "89")],
        ).expect("Failed to hset");

        // Set a TTL of 60 seconds on two fields of the hash.
        match r.hexpire("sensor:sensor1", 60, redis::ExpireOption::NONE, &["air_quality", "battery_level"]) {
            Ok(res18) => {
                let res18: Vec<i64> = res18;
                println!("{:?}", res18);    // >>> [1, 1]
            },
            Err(e) => {
                println!("Error setting expiration: {e}");
                return;
            }
        }

        // Retrieve the remaining TTL for those fields.
        match r.httl("sensor:sensor1", &["air_quality", "battery_level"]) {
            Ok(res19) => {
                let res19: Vec<i64> = res19;
                println!("{}", res19.len());    // >>> 2
            },
            Err(e) => {
                println!("Error getting TTL: {e}");
                return;
            }
        }
        let _: () = r.del("sensor:sensor1").await.expect("Failed to del");
        let _: () = r.hset_multiple(
            "sensor:sensor1",
            &[("air_quality", "256"), ("battery_level", "89")],
        ).await.expect("Failed to hset");

        // Set a TTL of 60 seconds on two fields of the hash.
        match r.hexpire("sensor:sensor1", 60, redis::ExpireOption::NONE, &["air_quality", "battery_level"]).await {
            Ok(res18) => {
                let res18: Vec<i64> = res18;
                println!("{:?}", res18);    // >>> [1, 1]
            },
            Err(e) => {
                println!("Error setting expiration: {e}");
                return;
            }
        }

        // Retrieve the remaining TTL for those fields.
        match r.httl("sensor:sensor1", &["air_quality", "battery_level"]).await {
            Ok(res19) => {
                let res19: Vec<i64> = res19;
                println!("{}", res19.len());    // >>> 2
            },
            Err(e) => {
                println!("Error getting TTL: {e}");
                return;
            }
        }

Set a hash field's TTL in milliseconds and then retrieve the remaining TTL in milliseconds:

Field expiration: Set a TTL in milliseconds on a hash field using HPEXPIRE, then read the remaining TTL with HPTTL
DEL sensor:sensor1 HSET sensor:sensor1 air_quality 256 battery_level 89 HPEXPIRE sensor:sensor1 60000 FIELDS 1 air_quality HPTTL sensor:sensor1 FIELDS 1 air_quality
r.delete("sensor:sensor1")
r.hset("sensor:sensor1", mapping={"air_quality": 256, "battery_level": 89})

# Set the TTL of the 'air_quality' field in milliseconds.
res20 = r.hpexpire("sensor:sensor1", 60000, "air_quality")
print(res20)
# >>> [1]

# Retrieve the remaining TTL in milliseconds.
res21 = r.hpttl("sensor:sensor1", "air_quality")
print(res21)
# >>> [59994]
# (your actual value may vary)
await client.del('sensor:sensor1')
await client.hSet('sensor:sensor1', { 'air_quality': 256, 'battery_level': 89 })

// Set the TTL of the 'air_quality' field in milliseconds.
const res20 = await client.hpExpire('sensor:sensor1', ['air_quality'], 60000)
console.log(res20)  // [1]

// Retrieve the remaining TTL in milliseconds.
const res21 = await client.hpTTL('sensor:sensor1', ['air_quality'])
console.log(res21)  // [59994] (your actual value may vary)
      jedis.del("sensor:sensor1");
      jedis.hset("sensor:sensor1", sensor1);

      // Set the TTL of the 'air_quality' field in milliseconds.
      List<Long> res17 = jedis.hpexpire("sensor:sensor1", 60000, "air_quality");
      System.out.println(res17); // [1]

      // Retrieve the remaining TTL in milliseconds.
      List<Long> res18 = jedis.hpttl("sensor:sensor1", "air_quality");
      System.out.println(res18.size()); // 1
            CompletableFuture<Void> hpExpire = hExpire
                    .thenCompose(prev -> asyncCommands.del("sensor:sensor1"))
                    .thenCompose(delRes -> asyncCommands.hset("sensor:sensor1", sensor1))
                    // Set the TTL of the 'air_quality' field in milliseconds.
                    .thenCompose(hsetRes -> asyncCommands.hpexpire("sensor:sensor1", 60000, "air_quality"))
                    .thenCompose(res17 -> {
                        System.out.println(res17); // >>> [1]
                        // Retrieve the remaining TTL in milliseconds.
                        return asyncCommands.hpttl("sensor:sensor1", "air_quality");
                    })
                    .thenAccept(res18 -> System.out.println(res18.size()))
                    // >>> 1
                    .toCompletableFuture();
            // Set the TTL of the 'air_quality' field in milliseconds.
            Mono<List<Long>> hpExpire = reactiveCommands.del("sensor:sensor1")
                    .then(reactiveCommands.hset("sensor:sensor1", sensor1))
                    .then(reactiveCommands.hpexpire("sensor:sensor1", 60000, "air_quality").collectList())
                    .doOnNext(result -> {
                        System.out.println(result);
                        // >>> [1]
                    });

            hpExpire.block();

            // Retrieve the remaining TTL in milliseconds.
            Mono<List<Long>> hpTtl = reactiveCommands.hpttl("sensor:sensor1", "air_quality")
                    .collectList().doOnNext(result -> {
                        System.out.println(result.size());
                        // >>> 1
                    });

            hpTtl.block();
	rdb.Del(ctx, "sensor:sensor1")
	rdb.HSet(ctx, "sensor:sensor1", "air_quality", 256, "battery_level", 89)

	// Set the TTL of the 'air_quality' field in milliseconds.
	res20, err := rdb.HPExpire(ctx, "sensor:sensor1", 60000*time.Millisecond,
		"air_quality").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res20) // >>> [1]

	// Retrieve the remaining TTL in milliseconds (returns one value per field).
	res21, err := rdb.HPTTL(ctx, "sensor:sensor1", "air_quality").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(len(res21)) // >>> 1
        db.KeyDelete("sensor:sensor1");
        db.HashSet("sensor:sensor1", [
            new("air_quality", 256),
            new("battery_level", 89)
        ]);

        // Set the TTL of the 'air_quality' field, expressed in milliseconds.
        ExpireResult[] hpexpireResult = db.HashFieldExpire(
            "sensor:sensor1",
            new RedisValue[] { "air_quality" },
            TimeSpan.FromMilliseconds(60000)
        );
        Console.WriteLine(string.Join(", ", hpexpireResult));
        // Success

        // Retrieve the remaining TTL.
        long[] hpttlResult = db.HashFieldGetTimeToLive(
            "sensor:sensor1",
            new RedisValue[] { "air_quality" }
        );
        Console.WriteLine(hpttlResult.Length);
        // 1
        $r->del('sensor:sensor1');
        $r->hmset('sensor:sensor1', [
            'air_quality' => 256,
            'battery_level' => 89,
        ]);

        // Set the TTL of the 'air_quality' field in milliseconds.
        $res17 = $r->hpexpire('sensor:sensor1', 60000, ['air_quality']);
        echo json_encode($res17) . PHP_EOL;
        // >>> [1]

        // Retrieve the remaining TTL in milliseconds.
        $res18 = $r->hpttl('sensor:sensor1', ['air_quality']);
        echo count($res18) . PHP_EOL;
        // >>> 1
        let _: () = r.del("sensor:sensor1").expect("Failed to del");
        let _: () = r.hset_multiple(
            "sensor:sensor1",
            &[("air_quality", "256"), ("battery_level", "89")],
        ).expect("Failed to hset");

        // Set the TTL of the 'air_quality' field in milliseconds.
        match r.hpexpire("sensor:sensor1", 60000, redis::ExpireOption::NONE, &["air_quality"]) {
            Ok(res20) => {
                let res20: Vec<i64> = res20;
                println!("{:?}", res20);    // >>> [1]
            },
            Err(e) => {
                println!("Error setting expiration: {e}");
                return;
            }
        }

        // Retrieve the remaining TTL in milliseconds.
        match r.hpttl("sensor:sensor1", &["air_quality"]) {
            Ok(res21) => {
                let res21: Vec<i64> = res21;
                println!("{}", res21.len());    // >>> 1
            },
            Err(e) => {
                println!("Error getting PTTL: {e}");
                return;
            }
        }
        let _: () = r.del("sensor:sensor1").await.expect("Failed to del");
        let _: () = r.hset_multiple(
            "sensor:sensor1",
            &[("air_quality", "256"), ("battery_level", "89")],
        ).await.expect("Failed to hset");

        // Set the TTL of the 'air_quality' field in milliseconds.
        match r.hpexpire("sensor:sensor1", 60000, redis::ExpireOption::NONE, &["air_quality"]).await {
            Ok(res20) => {
                let res20: Vec<i64> = res20;
                println!("{:?}", res20);    // >>> [1]
            },
            Err(e) => {
                println!("Error setting expiration: {e}");
                return;
            }
        }

        // Retrieve the remaining TTL in milliseconds.
        match r.hpttl("sensor:sensor1", &["air_quality"]).await {
            Ok(res21) => {
                let res21: Vec<i64> = res21;
                println!("{}", res21.len());    // >>> 1
            },
            Err(e) => {
                println!("Error getting PTTL: {e}");
                return;
            }
        }

Set a hash field's expiration to a specific timestamp and then retrieve that expiration time (both as a Unix time in seconds):

Field expiration: Set an absolute expiration timestamp on a hash field using HEXPIREAT, then read it back with HEXPIRETIME
DEL sensor:sensor1 HSET sensor:sensor1 air_quality 256 battery_level 89 HEXPIREAT sensor:sensor1 4102444800 FIELDS 1 air_quality HEXPIRETIME sensor:sensor1 FIELDS 1 air_quality
r.delete("sensor:sensor1")
r.hset("sensor:sensor1", mapping={"air_quality": 256, "battery_level": 89})

# Set the expiration of 'air_quality' to a Unix time 24 hours from now.
res22 = r.hexpireat(
    "sensor:sensor1",
    int(time.time()) + 24 * 60 * 60,
    "air_quality",
)
print(res22)
# >>> [1]

# Retrieve the expiration time as a Unix timestamp in seconds.
res23 = r.hexpiretime("sensor:sensor1", "air_quality")
print(res23)
# >>> [1717668041]
# (your actual value will vary)
await client.del('sensor:sensor1')
await client.hSet('sensor:sensor1', { 'air_quality': 256, 'battery_level': 89 })

// Set the expiration of 'air_quality' to a Unix time 24 hours from now.
const expireAt = Math.floor(Date.now() / 1000) + 24 * 60 * 60
const res22 = await client.hExpireAt('sensor:sensor1', ['air_quality'], expireAt)
console.log(res22)  // [1]

// Retrieve the expiration time as a Unix timestamp in seconds.
const res23 = await client.hExpireTime('sensor:sensor1', ['air_quality'])
console.log(res23)  // [1717668041] (your actual value will vary)
      jedis.del("sensor:sensor1");
      jedis.hset("sensor:sensor1", sensor1);

      // Set the expiration of 'air_quality' to a Unix time 24 hours from now.
      long expireAt = System.currentTimeMillis() / 1000L + 24 * 60 * 60;
      List<Long> res19 = jedis.hexpireAt("sensor:sensor1", expireAt, "air_quality");
      System.out.println(res19); // [1]

      // Retrieve the expiration time as a Unix timestamp in seconds.
      List<Long> res20 = jedis.hexpireTime("sensor:sensor1", "air_quality");
      System.out.println(res20.size()); // 1
            long expireAtSeconds = System.currentTimeMillis() / 1000L + 24 * 60 * 60;
            CompletableFuture<Void> hExpireAt = hpExpire
                    .thenCompose(prev -> asyncCommands.del("sensor:sensor1"))
                    .thenCompose(delRes -> asyncCommands.hset("sensor:sensor1", sensor1))
                    // Set the expiration of 'air_quality' to a Unix time 24 hours from now.
                    .thenCompose(hsetRes -> asyncCommands.hexpireat("sensor:sensor1", expireAtSeconds, "air_quality"))
                    .thenCompose(res19 -> {
                        System.out.println(res19); // >>> [1]
                        // Retrieve the expiration time as a Unix timestamp in seconds.
                        return asyncCommands.hexpiretime("sensor:sensor1", "air_quality");
                    })
                    .thenAccept(res20 -> System.out.println(res20.size()))
                    // >>> 1
                    .toCompletableFuture();
            long expireAtSeconds = System.currentTimeMillis() / 1000L + 24 * 60 * 60;

            // Set the expiration of 'air_quality' to a Unix time 24 hours from now.
            Mono<List<Long>> hExpireAt = reactiveCommands.del("sensor:sensor1")
                    .then(reactiveCommands.hset("sensor:sensor1", sensor1))
                    .then(reactiveCommands.hexpireat("sensor:sensor1", expireAtSeconds, "air_quality").collectList())
                    .doOnNext(result -> {
                        System.out.println(result);
                        // >>> [1]
                    });

            hExpireAt.block();

            // Retrieve the expiration time as a Unix timestamp in seconds.
            Mono<List<Long>> hExpireTime = reactiveCommands.hexpiretime("sensor:sensor1", "air_quality")
                    .collectList().doOnNext(result -> {
                        System.out.println(result.size());
                        // >>> 1
                    });

            hExpireTime.block();
	rdb.Del(ctx, "sensor:sensor1")
	rdb.HSet(ctx, "sensor:sensor1", "air_quality", 256, "battery_level", 89)

	// Set the expiration of 'air_quality' to a time 24 hours from now.
	res22, err := rdb.HExpireAt(ctx, "sensor:sensor1", time.Now().Add(24*time.Hour),
		"air_quality").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(res22) // >>> [1]

	// Retrieve the expiration time as a Unix timestamp (returns one value per field).
	res23, err := rdb.HExpireTime(ctx, "sensor:sensor1", "air_quality").Result()

	if err != nil {
		panic(err)
	}

	fmt.Println(len(res23)) // >>> 1
        db.KeyDelete("sensor:sensor1");
        db.HashSet("sensor:sensor1", [
            new("air_quality", 256),
            new("battery_level", 89)
        ]);

        // Set the expiration of 'air_quality' to a time 24 hours from now.
        ExpireResult[] hexpireAtResult = db.HashFieldExpire(
            "sensor:sensor1",
            new RedisValue[] { "air_quality" },
            DateTime.UtcNow.AddHours(24)
        );
        Console.WriteLine(string.Join(", ", hexpireAtResult));
        // Success

        // Retrieve the expiration time as a Unix timestamp.
        long[] hexpireTimeResult = db.HashFieldGetExpireDateTime(
            "sensor:sensor1",
            new RedisValue[] { "air_quality" }
        );
        Console.WriteLine(hexpireTimeResult.Length);
        // 1
        $r->del('sensor:sensor1');
        $r->hmset('sensor:sensor1', [
            'air_quality' => 256,
            'battery_level' => 89,
        ]);

        // Set the expiration of 'air_quality' to a Unix time 24 hours from now.
        $res19 = $r->hexpireat('sensor:sensor1', time() + 24 * 60 * 60, ['air_quality']);
        echo json_encode($res19) . PHP_EOL;
        // >>> [1]

        // Retrieve the expiration time as a Unix timestamp in seconds.
        $res20 = $r->hexpiretime('sensor:sensor1', ['air_quality']);
        echo count($res20) . PHP_EOL;
        // >>> 1
        let _: () = r.del("sensor:sensor1").expect("Failed to del");
        let _: () = r.hset_multiple(
            "sensor:sensor1",
            &[("air_quality", "256"), ("battery_level", "89")],
        ).expect("Failed to hset");

        // Set the expiration of 'air_quality' to a Unix time 24 hours from now.
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("Time went backwards")
            .as_secs() as i64;

        match r.hexpire_at("sensor:sensor1", now + 24 * 60 * 60, redis::ExpireOption::NONE, &["air_quality"]) {
            Ok(res22) => {
                let res22: Vec<i64> = res22;
                println!("{:?}", res22);    // >>> [1]
            },
            Err(e) => {
                println!("Error setting expiration: {e}");
                return;
            }
        }

        // Retrieve the expiration time as a Unix timestamp in seconds.
        match r.hexpire_time("sensor:sensor1", &["air_quality"]) {
            Ok(res23) => {
                let res23: Vec<i64> = res23;
                println!("{}", res23.len());    // >>> 1
            },
            Err(e) => {
                println!("Error getting expiration time: {e}");
                return;
            }
        }
        let _: () = r.del("sensor:sensor1").await.expect("Failed to del");
        let _: () = r.hset_multiple(
            "sensor:sensor1",
            &[("air_quality", "256"), ("battery_level", "89")],
        ).await.expect("Failed to hset");

        // Set the expiration of 'air_quality' to a Unix time 24 hours from now.
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .expect("Time went backwards")
            .as_secs() as i64;

        match r.hexpire_at("sensor:sensor1", now + 24 * 60 * 60, redis::ExpireOption::NONE, &["air_quality"]).await {
            Ok(res22) => {
                let res22: Vec<i64> = res22;
                println!("{:?}", res22);    // >>> [1]
            },
            Err(e) => {
                println!("Error setting expiration: {e}");
                return;
            }
        }

        // Retrieve the expiration time as a Unix timestamp in seconds.
        match r.hexpire_time("sensor:sensor1", &["air_quality"]).await {
            Ok(res23) => {
                let res23: Vec<i64> = res23;
                println!("{}", res23.len());    // >>> 1
            },
            Err(e) => {
                println!("Error getting expiration time: {e}");
                return;
            }
        }

Compact hashes

Redis 8.10 introduced compact hashes, a way to reduce the memory used by hashes that share the same field names. When many hashes have the same layout (for example, one hash per user, each with name, email, and age), Redis can store the shared set of field names once and keep only the values, plus a small reference to that set, in each key. This is an internal encoding: existing hash commands keep working exactly as before, with the same semantics and replies. The hash just uses less memory.

Compact hashes work well when:

  • Many keys share the same, mostly stable set of field names — classic object mapping, such as one hash per user, order, or session. The more keys that share a field set, the greater the memory saving.
  • Reads and value updates stay as fast as on a regular hash. Reading any field (for example, HGET, HGETALL, or HRANDFIELD) and overwriting an existing field's value (HSET key field value where field already exists) are unaffected.

Compact hashes are not a good fit when:

  • Field names change often. Adding a new field with HSET or removing one with HDEL detaches the key from its shared field-name set and re-resolves it against the new set, creating a new one if that layout hasn't been seen before. A workload that constantly adds or removes field names erodes the benefit. Note also that once a hash becomes a compact hash, it stays one for the life of the key: it moves between field-name sets as its fields change, but never reverts to a plain hash.
  • Field names are unique or highly dynamic per key. With little sharing, a key ends up with its own field-name set, which carries some metadata overhead. A set used by only a few keys can therefore consume more memory than a plain hash. Compact hashes pay off when hundreds or thousands of keys share a set.
  • Hashes are very large. Because a compact hash is transferred as a single blob during replication and slot migration, very large hash keys may reduce or negate the benefit.

There are two ways to opt in.

Bulk import with HIMPORT

The HIMPORT command family lets a client import many hashes that share the same field names efficiently. You declare the shared field names once with HIMPORT PREPARE, then create each key with HIMPORT SET by sending only its values. This reduces network traffic and per-command work compared with running HSET once per key, and hints Redis to store the new keys as compact hashes. See HIMPORT for the full workflow and its subcommands.

Automatic conversion

For workloads that can't adopt a new command, Redis can convert eligible hashes to compact hashes on its own, with no code change. All the following settings default to 0 (off) and can be changed at runtime with CONFIG SET.

On the write path

These convert hashes created or modified by normal commands (such as HSET), so an existing application gains the memory saving with no code change. They take effect lazily, like hash-max-listpack-entries: a change applies to a given hash only on its next write, not the moment you run CONFIG SET.

Config Meaning
hash-min-template-entries Minimum field count for a hash to be auto-converted to a compact hash on its next write. 0 disables auto-conversion.
hash-max-template-entries Maximum field count for auto-conversion: a hash wider than this is left a plain hash (keeps very wide hashes out of the shared registry). 0 means no upper bound.

A hash is not converted if it uses field expiration, even when its field count meets the minimum. After a hash has been converted, it stays a compact hash even if its field count later grows beyond hash-max-template-entries.

On RDB load

An RDB saved before this feature contains only plain hashes. These configs let Redis convert them to compact hashes as the RDB loads, so an upgrade reclaims memory without rewriting data. They apply only to RDBs without compact hashes; an RDB that already contains them is loaded as-is, with no load-time conversion. In effect these are one-time upgrade configs: they have no effect once the RDB contains at least one compact hash.

Config Meaning
hash-rdb-load-min-template-entries Minimum field count to convert a plain hash to a compact hash during load. 0 disables load-time conversion.
hash-rdb-load-max-template-entries Maximum field count for load-time conversion. 0 means no upper bound.
hash-rdb-load-template-disassembly-threshold Minimum number of keys that must end up sharing a converted field-name set for it to be kept. 0 keeps every converted set.

The disassembly threshold avoids wasting memory on field-name sets shared by only a few keys: at the end of the load, if a set is used by fewer keys than the threshold, those keys are converted back to plain hashes and the shared set is freed. It also acts as a safety valve during the load. Redis tracks how many converted sets are still below the threshold; once at least 1,000 have been created, if more than half of them are still below the threshold, Redis assumes the dataset is a poor fit for compact hashes and stops creating new ones partway through the load. From that point, only hashes whose field set matches one already created during this load are converted; the rest stay plain.

Performance

Most Redis hash commands are O(1).

A few commands, such as HKEYS, HVALS, HGETALL, and most of the expiration-related commands, are O(n), where n is the number of field-value pairs.

Limits

Every hash can store up to 4,294,967,295 (2^32 - 1) field-value pairs. In practice, your hashes are limited only by the overall memory on the VMs hosting your Redis deployment.

Learn more


  1. all timestamps are specified in seconds or milliseconds since the Unix epoch↩︎

RATE THIS PAGE
Back to top ↑