August 31, 2020·4 min read·engineeringcockroachdbmemoryquery-planning

CockroachDB 20.1.5 copies JSON bytes to retain less memory

Copying encoded JSON breaks a reference to a much larger key-value batch. The maintenance release also fixes month subtraction and avoids blocking query planning during statistics refresh.

Avoiding a copy can make memory usage worse when a small value keeps a large buffer alive. CockroachDB 20.1.5 addresses that problem in JSON table scans. Jordan Lewis's JSON decoding patch explains that lazy JSON values retained references to encoded bytes in a backing key-value batch. A single JSON value could therefore retain up to ten megabytes of batch data, while the memory accounting system could not recognize the retained allocation correctly.

The release catalog dates 20.1.5 to August 31. The JSON change is particularly useful because its mechanism contradicts a common allocation shortcut. Eliminating a copy reduces immediate copying work, but it can extend the lifetime of unrelated memory sharing the same backing allocation. The lifetime of that allocation matters as much as the size of the visible value.

I accept CockroachDB's distributed coordination costs when they are explicit. Memory accounting needs the same clarity. A query's apparent JSON value size should not conceal a much larger retained batch that the resource accounting does not attribute correctly. Making ownership more explicit can be worth a small amount of additional copying.

Lazy decoding retained the input buffer

The patch preserves lazy JSON decoding but changes who owns its encoded bytes. Before constructing the JSON value, the decoder creates a fresh byte allocation and copies the encoded value into it. The JSON object can then retain that smaller allocation without retaining the entire input batch through the same reference.

The author explicitly identifies a tradeoff. Retained memory decreases at the cost of a slight reduction in JSON scan performance. That is a more precise claim than calling the change a general scan speed improvement. Its purpose is to avoid oversized retained allocations and severe under-accounting, while accepting the immediate work required to separate the value's lifetime from the batch's lifetime.

For an operator explaining a query's memory behaviour, the relevant quantity is consequently retained memory. Counting the logical bytes in selected JSON values does not explain how much backing storage remains reachable. A reference can keep an allocation alive even when most of its bytes are unrelated to the value that still needs it.

Month subtraction needs a valid month after normalization

The release also includes Oliver Tan's negative-duration arithmetic repair. Subtracting months from a date or timestamp with a day greater than twenty-eight could subtract an additional year. The patch normalizes the expected month after the remainder calculation so a negative result returns to the positive month range.

The tests include July dates shifted backward by twelve, eighteen and thirty months, as well as cases combining months and seconds. Those cases exercise the interaction between month selection and day adjustment. Months have different lengths, so calendar arithmetic needs an expected month against which to adjust the resulting day.

This is a result-correctness repair, separate from the JSON memory change. A query can finish without a resource error and still return the wrong calendar value if that normalization is incorrect. The maintenance release therefore matters to applications using month-based periods even when they do not store JSON or run memory-intensive scans.

Statistics refresh stops blocking unrelated planning work

Rebecca Taft's statistics-cache patch changes how newly available table statistics reach the planner. Previously, adding statistics deleted the cached entry. The next query had to fetch replacement statistics before planning could proceed. Frequent statistics updates and contention on the statistics table could therefore cause latency spikes.

The new behaviour refreshes asynchronously. Existing cached statistics remain usable while the replacement is fetched, and the cache receives the new values when they are available. Query planning no longer has to wait merely because the old statistics became stale before the new copy reached memory.

This also changes the interpretation of a delay near a statistics update. A query can now choose a plan using the existing estimates during the refresh window. Its plan may therefore differ from one chosen after the replacement arrives. That timing follows the documented refresh policy, rather than indicating that the statistics update was ignored.

I favour the explicit distinction between stale planning estimates and unavailable data. The patch changes which estimates the planner can use temporarily. It does not announce that application reads may return an earlier database value. Statistics guide plan selection, while the query still needs to evaluate the data according to its transaction semantics.

The resulting maintenance changes act at different points in query processing. JSON decoding changes allocation ownership during execution, calendar normalization repairs an expression's result, and statistics refresh removes an avoidable wait during planning. Each has a specific consequence that can explain an observed problem without assuming the entire query engine became uniformly faster.