Custom field types which stores dictionary as JSON serialized strings. This is done because storing large objects as JSON serialized strings is much more fficient on the serialize and unserialize paths compared to used EscapedDictField which needs to escape all the special values (
| 330 | |
| 331 | |
| 332 | class JSONDictField(BinaryField): |
| 333 | """ |
| 334 | Custom field types which stores dictionary as JSON serialized strings. |
| 335 | |
| 336 | This is done because storing large objects as JSON serialized strings is much more fficient |
| 337 | on the serialize and unserialize paths compared to used EscapedDictField which needs to escape |
| 338 | all the special values ($, .). |
| 339 | |
| 340 | Only downside is that to MongoDB those values are plain raw strings which means you can't query |
| 341 | on actual dictionary field values. That's not an issue for us, because in places where we use |
| 342 | it, those values are already treated as plain binary blobs to the database layer and we never |
| 343 | directly query on those field values. |
| 344 | |
| 345 | In micro benchmarks we have seen speed ups for up to 10x on write path and up to 6x on read |
| 346 | path. Change also scaled down which means it didn't add any additional overhead for very small |
| 347 | results - in fact, it was also faster for small results dictionaries |
| 348 | |
| 349 | More context and numbers are available at https://github.com/StackStorm/st2/pull/4846. |
| 350 | |
| 351 | NOTES, LIMITATIONS: |
| 352 | |
| 353 | This field type can only be used on dictionary values on which we don't perform direct database |
| 354 | queries (aka filter on a specific dictionary item value or similar). |
| 355 | |
| 356 | Good examples of those are "result" field on ExecutionDB, LiveActionDB and TaskExecutionDB, |
| 357 | "output" on WorkflowExecutionDB, etc. |
| 358 | |
| 359 | IMPLEMENTATION DETAILS: |
| 360 | |
| 361 | If header is used, values are stored in the following format: |
| 362 | <compression type>:<serialization type>:<serialized binary data>. |
| 363 | |
| 364 | For example: |
| 365 | |
| 366 | n:o:... - No compression, (or)json serialization |
| 367 | z:o:... - Zstandard compression, (or)json serialization |
| 368 | |
| 369 | If header is not used, value is stored as a serialized JSON string of the input dictionary. |
| 370 | """ |
| 371 | |
| 372 | def __init__(self, *args, **kwargs): |
| 373 | # True if we should use field header which is more future proof approach and also allows |
| 374 | # us to support optional per-field compression, etc. |
| 375 | # This option is only exposed so we can benchmark different approaches and how much overhead |
| 376 | # using a header adds. |
| 377 | self.use_header = kwargs.pop("use_header", False) |
| 378 | self.compression_algorithm = kwargs.pop("compression_algorithm", "none") |
| 379 | |
| 380 | super(JSONDictField, self).__init__(*args, **kwargs) |
| 381 | |
| 382 | def to_mongo(self, value): |
| 383 | if not isinstance(value, dict): |
| 384 | raise ValueError( |
| 385 | "value argument must be a dictionary (got: %s)" % type(value) |
| 386 | ) |
| 387 | |
| 388 | data = self._serialize_field_value(value) |
| 389 | return data |
no outgoing calls