github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/docs/RFCS/20171005_jsonb_encoding.md (about) 1 - Feature Name: JSONB Encoding 2 - Status: accepted 3 - Start Date: 2017-10-05 4 - Author: Justin Jaffray 5 - RFC PR: #19062 6 - Cockroach Issue: #2969 7 8 # Summary 9 10 Provide a result value and kv encoding for JSONB values as described in the 11 [scoping RFC](https://github.com/cockroachdb/cockroach/pull/18739). The 12 motivation and use-cases for including JSONB were covered in the scoping 13 RFC. As a result of this change, users will be able to manipulate JSONB values 14 both in SQL result values and in columns within tables. We opt to disallow key 15 encoding of JSONB values at this time, due to complications regarding the 16 key encoding of numeric values. 17 18 The format will be optimized to reduce unnecessary decoding of values. This is 19 because as opposed to array columns, we expect the common case of this format 20 to be extracting a subset of the fields, rather than the entire thing at once. 21 22 # Motivation 23 24 Covered in the scoping RFC. 25 26 # Reference-level explanation 27 28 There are several axes upon which a binary encoding for JSONB in CockroachDB 29 should be evaluated, among these are 30 31 * speed of access/decoding, 32 * speed of encoding, 33 * size of encoding (and compressibility), 34 * and flexibility. 35 36 In addition, as CockroachDB already provides a way to handle structured data 37 with a known set of fields (the relational model!), we should have an eye 38 towards the opposite use-case - unstructured data with an unknown (possibly 39 large) set of fields. 40 41 The proposed value encoding is to use that of Postgres, modified as appropriate 42 to fit into CockroachDB. This RFC will first describe the format in detail, 43 then go into the alternatives considered and why they were rejected. 44 45 ## JSONB Encoding 46 47 A JSON value can be one of seven types: 48 49 * `true`, 50 * `false`, 51 * `null`, 52 * a UTF-8 `string`, 53 * an arbitrary-precision `number`, 54 * an `array` of zero or more JSON values, 55 * or an `object` consisting of key-value pairs, where the keys are unique 56 strings and the values are JSON values. 57 58 Every JSON value save for the root has a corresponding header called a *JEntry*, 59 which is a 32-bit value encoding the following information: 60 ``` 61 0 000 0000000000000000000000000000 62 ^ ^ ^ 63 │ │ └─ 28 bits denoting either the length of this value, or its end+1 offset from 64 │ │ the beginning of the innermost container this value is within. 65 │ └─ 3 bits denoting the type of the value (six possible values - one of the 5 66 │ scalar types or a container) 67 └─ 1 bit denoting whether this entry stores a length or an offset. 68 ``` 69 70 The reason for the length vs. offset flag is explained in [this 71 comment](https://github.com/postgres/postgres/blob/90627cf98a8e7d0531789391fd798c9bfcc3bc1a/src/include/utils/jsonb.h#L113-L138). 72 It's not immediately clear that this compressibility trade-off is relevant for 73 CockroachDB, in particular given that we use different compression algorithms than 74 Postgres. 75 The [rationale section](#rationale) includes the results of some rough experiments with 76 Snappy (the compression algorithm used in CockroachDB), but more detailed 77 experiments will be needed. 78 79 A *container header* is a 32-bit value with the following format: 80 ``` 81 000 00000000000000000000000000000 82 ^ ^ 83 │ │ 84 │ │ 85 │ └─ 29 bits denoting the number of elements in an array, or the number of 86 │ key-value pairs in an object. 87 └─ 3 bits denoting the type of this container (`array`, `object`, or `scalar`) 88 ``` 89 90 The root JSON object is always a container, but it might be a `scalar` 91 container (defined below). 92 93 ### `true`, `false`, `null` 94 95 The values `true`, `false`, and `null` are expressed entirely by their type. 96 Their encoding is the empty sequence of bytes. 97 98 ### `string` 99 100 `string`s are encoded as normal UTF-8 strings. 101 102 ### `number` 103 104 In Postgres, `number` maps on to the Postgres `NUMERIC` type. Thus, this 105 differs from JavaScript's `number` type as Postgres JSONB `number`s are 106 arbitrary-precision. 107 In CockroachDB, `number` will be encoded using CockroachDB's `DECIMAL` encoding. 108 109 We can experiment, using the remaining free type tags, with possibly encoding 110 numbers as plain int64s or float64s where possible, to see if this gives a 111 significant improvement in either encoding size or decoding speed. 112 113 ### `array` 114 115 An `array` is stored as 116 * a container header tagged as an array, containing the number of elements in 117 the array, 118 * the JEntry header of every element, with every `$STRIDE`th JEntry storing an 119 offset, and every other one storing a length, and then finally 120 * the encodings of the elements themselves. 121 122 ### `object` 123 124 An `object` is stored as 125 * a container header tagged as an object, containing the number of 126 key-value pairs in the object, then 127 * the JEntry header of every key (which are sorted, unique strings) followed by 128 the JEntry header of every value, with every `$STRIDE`th JEntry storing an 129 offset, and every other one storing a length, then finally 130 * the encoding of every key followed by the encoding of every value (with the 131 `i`th key mapping to the `i`th value). 132 133 ### `scalar` 134 135 The `scalar` container type exists so that the root value has a place to put 136 its JEntry (which is needed if it isn't an `object` or an `array`). It only 137 occurs at the root and appears only if the root value is `true`, `false`, 138 `null`, a `string`, or a `number`. It contains exactly one value and its 139 encoding consists of 140 * a container header tagged as a scalar, 141 * the JEntry for the singular value contained within, 142 * the encoding of the contained value. 143 144 ## Examples 145 146 ### encode(`null`) 147 148 ``` 149 0x10000000 <- scalar container header 150 0x40000000 <- JEntry header signifying null type and 0 length 151 (no encoding) 152 ``` 153 154 ### encode(`[true, "hello", {"a": "b"}]`) 155 156 (This glosses over numerics because their particular encoding already exists 157 within CockroachDB and is not very enlightening for this discussion.) 158 159 ``` 160 0x40000003 <- array container header (3 elements) 161 0x30000000 <- JEntry header of true 162 0x00000005 <- JEntry header of "hello" 163 0x5000000e <- JEntry header of {"a": "b"} (whose encoding is 0x0e bytes long) 164 (encoding of true, which is empty) 165 0x68656c6c6f <- encoding of "hello" 166 0x20000001 <- object container header (1 key-value pair) 167 0x30000001 <- JEntry header of "a" 168 0x30000001 <- JEntry header of "b" 169 0x61 <- encoding of "a" 170 0x62 <- encoding of "b" 171 ``` 172 173 ## Implementation 174 175 Implementation of JSONB will follow three major steps: 176 * begin with only JSONB result values which are ignorant to any encoding, 177 (similar to how CockroachDB supported array values before it supported array 178 column types), 179 * implement the encoding to-and-from the existing naive JSONB result values, 180 * modify the JSONB result values to take advantage of the encoding. 181 182 As the implementation of the JSONB result values can easily be changed in the 183 future (as opposed to the encoding, which cannot), it's less important to nail 184 down the precise implementation of them ahead of time, and they can be tuned 185 and optimized as time permits. 186 187 ## Rationale 188 189 ### Offset-vs-length 190 191 Some cursory benchmarks with Snappy imply a modest but significant improvement 192 in compressibility with values encoded with lengths vs. offsets (string 193 encoding and BSON included for interest's sake): 194 195 #### 1000 JSON objects with 200 keys, no keys in common across objects 196 ``` 197 compression of jsonb-len: 6838008B -> 4959971B (72.54%) 198 compression of jsonb-off: 6838008B -> 5779418B (84.52%) 199 compression of json-text: 7025581B -> 6596903B (93.90%) 200 compression of bson: 7566787B -> 5651655B (74.69%) 201 ``` 202 203 #### 1000 JSON objects with 200 keys, all keys in common across objects 204 ``` 205 compression of jsonb-len: 6944660B -> 3860875B (55.59%) 206 compression of jsonb-off: 6944660B -> 4911796B (70.73%) 207 compression of json-text: 7249122B -> 5841104B (80.58%) 208 compression of bson: 7726628B -> 4713029B (61.00%) 209 ``` 210 211 Comparable results were seen comparing the compression of RocksDB SSTables, so 212 this seems like a reasonable concern. 213 214 `$STRIDE` is a tunable value, which will have to be chosen after some 215 benchmarking of the compressibility vs. performance trade-off, along with 216 information from customers on the shape of their JSON data. 217 218 Initially we will set it to be infinite, always using lengths. If this proves 219 to be bad for lookup performance we can tweak it as need be. 220 221 ## Alternative Encodings 222 223 ### String-encoding 224 225 The simplest way we could represent JSON is simply as text. This is the way 226 the SQL/JSON spec says it should be done and was how Postgres's initial 227 implementation worked (their `JSON` as opposed to `JSONB`), however, this has 228 two primary undesirable properties. First, it's quite bloated (and not very 229 compressible under Snappy). More problematic however is the access speed. 230 Extracting a field from a JSON object stored this way requires parsing the 231 object, something we would like to avoid in the common case. 232 233 ### BSON 234 235 BSON is the encoding used in MongoDB. 236 Its design is documented at [bsonspec.org](http://bsonspec.org/spec.html). 237 This format is proven and well-understood. 238 It seems optimized for documents with small sets of keys, as such, it doesn't 239 contain an index of entries at the beginning of a document, and it doesn't 240 mandate that keys be sorted. This makes sense, as Mongo doesn't support the type of 241 inverted indexing Postgres supports and thus expects users to have a more 242 structured schema with a small set of keys. 243 As we are designing for use-cases more similar to Postgres, these 244 considerations don't seem as valuable for us. 245 246 # Unresolved questions