Secondary indexes

On top of the key value store immudb provides secondary indexes to help developers to handle complex queries.

Sorted sets

The sorted set data type provides simplest secondary index you can create with immudb. That’s a data structure that represents a set of elements ordered by the score of each element, which is a floating point number. The score type is a float64 to accommodate the maximum number of uses cases. 64-bit floating point gives a lot of flexibility and dynamic range, at the expense of having only 53-bits of integer.

When an integer64 is cast to a float there could be a loss of precision, but the insertion order is guaranteed by the internal database index that is appended to the internal index key.

ZAdd can reference an item by key or by index.

ZScan accepts following arguments:

  • Set: the name of the collection
  • SeekKey: initial key for the first entry in the iteration. Optional
  • SeekScore: the min or max score for the first entry in the iteration, depending on Desc value. Optional
  • SeekAtTx: the tx id for the first entry in the iteration. Optional
  • InclusiveSeek: the element resulting from the combination of the SeekKey SeekScore and SeekAtTx is returned with the result. Optional
  • Desc: DESC or ASC sorting order. Optional
  • SinceTx: immudb will wait that the transaction provided by SinceTx be processed. Optional
  • NoWait: when true scan doesn’t wait that txSinceTx is processed. Optional
  • MinScore: minimum score filter. Optional
  • MaxScore: maximum score filter. Optional
  • Limit: maximum number of returned items. Optional

Having the possibility to get data specifying a transaction id: AtTx, it’s the optimal way to retrieve the data, as it can be done with random access to it. And it can be made immediately after the transaction was committed or at any point in the future. When the transaction ID is unknown by the application and the query is made by key or key prefixes, it will be served through the index, depending on the insertion rate, it can be delayed or up to date with inserted data, using a big number in SinceTx with NoWait in true will mean that the query will be resolved by looking at the most recent indexed data, but if your query needs to be resolved after some transactions has been inserted, you can set SinceTx to specify up to which transaction the index has to be made for resolving it.

  1. i1, err := client.Set(ctx, []byte(`user1`), []byte(`user1@mail.com`))
  2. if err != nil{
  3. log.Fatal(err)
  4. }
  5. i2, err := client.Set(ctx, []byte(`user2`), []byte(`user2@mail.com`))
  6. if err != nil{
  7. log.Fatal(err)
  8. }
  9. i3, err := client.Set(ctx, []byte(`user3`), []byte(`user3@mail.com`))
  10. if err != nil{
  11. log.Fatal(err)
  12. }
  13. i4, err := client.Set(ctx, []byte(`user3`), []byte(`another-user3@mail.com`))
  14. if err != nil{
  15. log.Fatal(err)
  16. }
  17. if _ , err = client.ZAddAt(ctx, []byte(`age`), 25, []byte(`user1`), i1.Id); err != nil {
  18. log.Fatal(err)
  19. }
  20. if _ , err = client.ZAddAt(ctx, []byte(`age`), 36, []byte(`user2`), i2.Id); err != nil {
  21. log.Fatal(err)
  22. }
  23. if _ , err = client.ZAddAt(ctx, []byte(`age`), 36, []byte(`user3`), i3.Id); err != nil {
  24. log.Fatal(err)
  25. }
  26. if _ , err = client.ZAddAt(ctx, []byte(`age`), 54, []byte(`user3`), i4.Id); err != nil {
  27. log.Fatal(err)
  28. }
  29. zscanOpts1 := &schema.ZScanRequest{
  30. Set: []byte(`age`),
  31. SinceTx: math.MaxUint64,
  32. NoWait: true,
  33. MinScore: &schema.Score{Score: 36},
  34. }
  35. the36YearsOldList, err := client.ZScan(ctx, zscanOpts1)
  36. if err != nil{
  37. log.Fatal(err)
  38. }
  39. s, _ := json.MarshalIndent(the36YearsOldList, "", "\t")
  40. fmt.Print(string(s))
  41. oldestReq := &schema.ZScanRequest{
  42. Set: []byte(`age`),
  43. SeekKey: []byte{0xFF},
  44. SeekScore: math.MaxFloat64,
  45. SeekAtTx: math.MaxUint64,
  46. Limit: 1,
  47. Desc: true,
  48. SinceTx: math.MaxUint64,
  49. NoWait: true,
  50. }
  51. oldest, err := client.ZScan(ctx, oldestReq)
  52. if err != nil{
  53. log.Fatal(err)
  54. }
  55. s, _ = json.MarshalIndent(oldest, "", "\t")
  56. fmt.Print(string(s))
  1. byte[] value1 = {0, 1, 2, 3};
  2. byte[] value2 = {4, 5, 6, 7};
  3. try {
  4. immuClient.set("zadd1", value1);
  5. immuClient.set("zadd2", value2);
  6. } catch (CorruptedDataException e) {
  7. // ...
  8. }
  9. TxMetadata set1TxMd = null;
  10. try {
  11. immuClient.zAdd("set1", 1, "zadd1");
  12. set1TxMd = immuClient.zAdd("set1", 2, "zadd2");
  13. immuClient.zAddAt("set1", 3, "zadd3", set1TxMd.id);
  14. immuClient.zAdd("set2", 2, "zadd1");
  15. immuClient.zAdd("set2", 1, "zadd2");
  16. } catch (CorruptedDataException e) {
  17. // ...
  18. }
  19. List<KV> zScan1 = immuClient.zScan("set1", set1TxMd.id, 5, false);
  20. // We expect two KVs with key names "zadd1" and "zadd2".
  21. List<KV> zScan2 = immuClient.zScan("set2", 5, false);
  22. // Same as before, we expect two KVs with key names "zadd2" and "zadd1".

This feature is not yet supported or not documented. Do you want to make a feature request or help out? Open an issue on Python sdk github projectSecondary indexes - 图1 (opens new window)

  1. import ImmudbClient from 'immudb-node'
  2. import Parameters from 'immudb-node/types/parameters'
  3. const IMMUDB_HOST = '127.0.0.1'
  4. const IMMUDB_PORT = '3322'
  5. const IMMUDB_USER = 'immudb'
  6. const IMMUDB_PWD = 'immudb'
  7. const cl = new ImmudbClient({ host: IMMUDB_HOST, port: IMMUDB_PORT });
  8. (async () => {
  9. await cl.login({ user: IMMUDB_USER, password: IMMUDB_PWD })
  10. const { id: id1 } = await cl.set({ key: 'user1', value: 'user1@mail.com' })
  11. const { id: id2 } = await cl.set({ key: 'user2', value: 'user2@mail.com' })
  12. const { id: id3 } = await cl.set({ key: 'user3', value: 'user3@mail.com' })
  13. const { id: id4 } = await cl.set({ key: 'user3', value: 'another-user3@mail.com' })
  14. const zAddAtReq1: Parameters.ZAddAt = {
  15. set: 'age',
  16. score: 25,
  17. key: 'user1',
  18. attx: id1
  19. }
  20. const zAddAtRes1 = await cl.zAddAt(zAddAtReq1)
  21. const zAddAtReq2: Parameters.ZAddAt = {
  22. set: 'age',
  23. score: 36,
  24. key: 'user2',
  25. attx: id2
  26. }
  27. const zAddAtRes2 = await cl.zAddAt(zAddAtReq2)
  28. const zAddAtReq3: Parameters.ZAddAt = {
  29. set: 'age',
  30. score: 36,
  31. key: 'user3',
  32. attx: id3
  33. }
  34. const zAddAtRes3 = await cl.zAddAt(zAddAtReq3)
  35. const zAddAtReq4: Parameters.ZAddAt = {
  36. set: 'age',
  37. score: 54,
  38. key: 'user4',
  39. attx: id4
  40. }
  41. const zAddAtRes4 = await cl.zAddAt(zAddAtReq4)
  42. const zScanReq: Parameters.ZScan = {
  43. set: 'age',
  44. sincetx: 0,
  45. nowait: true,
  46. minscore: {
  47. score: 36
  48. }
  49. }
  50. const zScanRes = await cl.zScan(zScanReq)
  51. console.log('success: zScan all 36-years-old users', zScanRes)
  52. })()

This feature is not yet supported or not documented. Do you want to make a feature request or help out? Open an issue on .Net sdk github projectSecondary indexes - 图2 (opens new window)

If you’re using another development language, please read up on our immugwSecondary indexes - 图3 (opens new window) option.