Add kCool Temperature (#14000)

Summary:
also requested by internal user, like kIce in https://github.com/facebook/rocksdb/issues/13927

Pull Request resolved: https://github.com/facebook/rocksdb/pull/14000

Test Plan: unit tests updated

Reviewed By: archang19

Differential Revision: D83200479

Pulled By: pdillinger

fbshipit-source-id: 31f2842d87bcad40227aeee9687ff5772393689c
This commit is contained in:
Peter Dillinger
2025-09-25 11:27:00 -07:00
committed by Facebook GitHub Bot
parent 90241e18c8
commit 1c8a012727
16 changed files with 129 additions and 53 deletions
+15 -8
View File
@@ -1764,9 +1764,10 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
options.env = mock_env_.get();
options.level0_file_num_compaction_trigger = kNumTrigger;
options.num_levels = kNumLevels;
// This existing test selected to also check the kIce case, which should not
// be interesting enough to exercise across all the test cases
options.last_level_temperature = Temperature::kIce;
// This existing test selected to also check the case of various temperatures
// for last_level_temperature, which should not be interesting enough to
// exercise across many/all test cases
options.last_level_temperature = RandomKnownTemperature();
DestroyAndReopen(options);
Random rnd(301);
@@ -1794,8 +1795,9 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
auto seqs = tp_mapping.TEST_GetInternalMapping();
ASSERT_FALSE(seqs.empty());
ASSERT_GE(GetSstSizeHelper(Temperature::kUnknown), 1);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kIce), 0);
for (auto t : kKnownTemperatures) {
ASSERT_EQ(GetSstSizeHelper(t), 0);
}
// Wait more than preclude_last_level time, then make sure all the data is
// compacted to the last level even there's no write (no seqno -> time
@@ -1804,9 +1806,14 @@ TEST_P(PrecludeLastLevelTest, SmallPrecludeTime) {
ASSERT_OK(db_->CompactRange(CompactRangeOptions(), nullptr, nullptr));
ASSERT_EQ("0,0,0,0,0,0,1", FilesPerLevel());
ASSERT_EQ(GetSstSizeHelper(Temperature::kUnknown), 0);
ASSERT_EQ(GetSstSizeHelper(Temperature::kCold), 0);
ASSERT_GE(GetSstSizeHelper(Temperature::kIce), 1);
for (auto t : kKnownTemperatures) {
if (t == options.last_level_temperature) {
ASSERT_GT(GetSstSizeHelper(t), 0);
} else {
ASSERT_EQ(GetSstSizeHelper(t), 0);
}
}
Close();
}
+50 -13
View File
@@ -9915,6 +9915,20 @@ static void VerifyTemperatureFileReadStats(const Statistics& st,
EXPECT_EQ(iostats->file_io_stats_by_temperature.warm_file_read_count, 0);
}
if (temps.Contains(Temperature::kCool)) {
EXPECT_GE(st.getTickerCount(COOL_FILE_READ_BYTES), min_bytes);
EXPECT_GE(st.getTickerCount(COOL_FILE_READ_COUNT), min_count);
EXPECT_GE(iostats->file_io_stats_by_temperature.cool_file_bytes_read,
min_bytes);
EXPECT_GE(iostats->file_io_stats_by_temperature.cool_file_read_count,
min_count);
} else {
EXPECT_EQ(st.getTickerCount(COOL_FILE_READ_BYTES), 0);
EXPECT_EQ(st.getTickerCount(COOL_FILE_READ_COUNT), 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.cool_file_bytes_read, 0);
EXPECT_EQ(iostats->file_io_stats_by_temperature.cool_file_read_count, 0);
}
if (temps.Contains(Temperature::kCold)) {
EXPECT_GE(st.getTickerCount(COLD_FILE_READ_BYTES), min_bytes);
EXPECT_GE(st.getTickerCount(COLD_FILE_READ_COUNT), min_count);
@@ -9945,7 +9959,7 @@ static void VerifyTemperatureFileReadStats(const Statistics& st,
}
TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
// Test multi-tier aging: Hot -> Warm -> Cold -> Ice
// Test multi-tier aging: Hot -> Warm -> Cool -> Cold -> Ice
Options options = CurrentOptions();
options.compaction_style = kCompactionStyleFIFO;
options.num_levels = 1;
@@ -9961,8 +9975,9 @@ TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
// Multi-tier aging: files age through multiple temperatures
fifo_options.file_temperature_age_thresholds = {
{Temperature::kWarm, 500}, // Hot -> Warm after 500s
{Temperature::kCold, 1000}, // Warm -> Cold after 1000s
{Temperature::kIce, 1500} // Cold -> Ice after 1500s
{Temperature::kCool, 1000}, // Warm -> Cool
{Temperature::kCold, 1500}, // Cool -> Cold
{Temperature::kIce, 2000} // Cold -> Ice
};
fifo_options.max_table_files_size = 100000000;
fifo_options.allow_trivial_copy_when_change_temperature = true;
@@ -9973,8 +9988,8 @@ TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
env_->SetMockSleep();
// Track all temperature file creations
int total_hot = 0, total_warm = 0, total_cold = 0, total_ice = 0,
total_unknown = 0;
int total_hot = 0, total_warm = 0, total_cool = 0, total_cold = 0,
total_ice = 0, total_unknown = 0;
ROCKSDB_NAMESPACE::SyncPoint::GetInstance()->SetCallBack(
"NewWritableFile::FileOptions.temperature", [&](void* arg) {
Temperature temperature = *(static_cast<Temperature*>(arg));
@@ -9985,6 +10000,9 @@ TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
case Temperature::kWarm:
total_warm++;
break;
case Temperature::kCool:
total_cool++;
break;
case Temperature::kCold:
total_cold++;
break;
@@ -10016,8 +10034,11 @@ TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kHot);
// Land well into each time interval
env_->MockSleepForSeconds(100);
// Age initial files to warm
env_->MockSleepForSeconds(600);
env_->MockSleepForSeconds(500);
ASSERT_OK(Put(Key(1), Random::GetTLSInstance()->RandomBinaryString(101)));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
@@ -10031,12 +10052,26 @@ TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
// Verify Warm file statistics
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kWarm);
// Age initial files to cold
env_->MockSleepForSeconds(600);
// Age initial files to cool
env_->MockSleepForSeconds(500);
ASSERT_OK(Put(Key(2), Random::GetTLSInstance()->RandomBinaryString(102)));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Test reading from Cool temperature file (the aged file)
ASSERT_OK(options.statistics->Reset());
get_iostats_context()->Reset();
ASSERT_EQ(100U, Get(Key(0)).size());
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kCool);
// Age initial files to cold
env_->MockSleepForSeconds(500);
ASSERT_OK(Put(Key(3), Random::GetTLSInstance()->RandomBinaryString(103)));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
// Test reading from Cold temperature file (the aged file)
ASSERT_OK(options.statistics->Reset());
get_iostats_context()->Reset();
@@ -10046,8 +10081,8 @@ TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
VerifyTemperatureFileReadStats(*options.statistics, Temperature::kCold);
// Age initial files to ice
env_->MockSleepForSeconds(600);
ASSERT_OK(Put(Key(3), Random::GetTLSInstance()->RandomBinaryString(103)));
env_->MockSleepForSeconds(500);
ASSERT_OK(Put(Key(4), Random::GetTLSInstance()->RandomBinaryString(104)));
ASSERT_OK(Flush());
ASSERT_OK(dbfull()->TEST_WaitForCompact());
@@ -10072,12 +10107,14 @@ TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
// Verify current files temperatures
EXPECT_EQ(temp_counts[Temperature::kHot], 1);
EXPECT_EQ(temp_counts[Temperature::kWarm], 1);
EXPECT_EQ(temp_counts[Temperature::kCool], 1);
EXPECT_EQ(temp_counts[Temperature::kCold], 1);
EXPECT_EQ(temp_counts[Temperature::kIce], 3);
// Verify historical (and current) file temperatures
EXPECT_EQ(total_hot, 6);
EXPECT_EQ(total_warm, 5);
EXPECT_EQ(total_hot, 7);
EXPECT_EQ(total_warm, 6);
EXPECT_EQ(total_cool, 5);
EXPECT_EQ(total_cold, 4);
EXPECT_EQ(total_ice, 3);
@@ -10087,7 +10124,7 @@ TEST_F(DBCompactionTest, FIFOMultiTierTemperatureAging) {
get_iostats_context()->Reset();
// Read from all files to verify cumulative statistics
for (int i = 0; i < 4; i++) {
for (int i = 0; i < 5; i++) {
ASSERT_EQ(static_cast<unsigned>(100 + i), Get(Key(i)).size());
}
+9 -16
View File
@@ -6063,16 +6063,9 @@ TEST_F(DBTest2, VariousFileTemperatures) {
};
// We don't have enough non-unknown temps to confidently distinguish that
// a specific setting caused a specific outcome, in a single run. This is a
// reasonable work-around without blowing up test time. Only returns
// non-unknown temperatures.
auto RandomTemp = [] {
static std::vector<Temperature> temps = {
Temperature::kHot, Temperature::kWarm, Temperature::kCold,
Temperature::kIce};
return temps[Random::GetTLSInstance()->Uniform(
static_cast<int>(temps.size()))];
};
// a specific setting caused a specific outcome, in a single run. Using
// RandomKnownTemperature() is a reasonable work-around without blowing up
// test time.
auto test_fs = std::make_shared<MyTestFS>(env_->GetFileSystem());
std::unique_ptr<Env> env(new CompositeEnvWrapper(env_, test_fs));
@@ -6088,22 +6081,22 @@ TEST_F(DBTest2, VariousFileTemperatures) {
options.env = env.get();
test_fs->Reset();
if (use_optimize) {
test_fs->optimize_manifest_temperature = RandomTemp();
test_fs->optimize_manifest_temperature = RandomKnownTemperature();
test_fs->expected_manifest_temperature =
test_fs->optimize_manifest_temperature;
test_fs->optimize_wal_temperature = RandomTemp();
test_fs->optimize_wal_temperature = RandomKnownTemperature();
test_fs->expected_wal_temperature = test_fs->optimize_wal_temperature;
}
if (use_temp_options) {
options.metadata_write_temperature = RandomTemp();
options.metadata_write_temperature = RandomKnownTemperature();
test_fs->expected_manifest_temperature =
options.metadata_write_temperature;
test_fs->expected_other_metadata_temperature =
options.metadata_write_temperature;
options.wal_write_temperature = RandomTemp();
options.wal_write_temperature = RandomKnownTemperature();
test_fs->expected_wal_temperature = options.wal_write_temperature;
options.last_level_temperature = RandomTemp();
options.default_write_temperature = RandomTemp();
options.last_level_temperature = RandomKnownTemperature();
options.default_write_temperature = RandomKnownTemperature();
}
DestroyAndReopen(options);
+9
View File
@@ -1872,4 +1872,13 @@ template class TargetCacheChargeTrackingCache<
CacheEntryRole::kBlockBasedTableReader>;
template class TargetCacheChargeTrackingCache<CacheEntryRole::kFileMetadata>;
const std::vector<Temperature> kKnownTemperatures = {
Temperature::kHot, Temperature::kWarm, Temperature::kCool,
Temperature::kCold, Temperature::kIce};
Temperature RandomKnownTemperature() {
return kKnownTemperatures[Random::GetTLSInstance()->Uniform(
static_cast<int>(kKnownTemperatures.size()))];
}
} // namespace ROCKSDB_NAMESPACE
+4
View File
@@ -1467,4 +1467,8 @@ class DBTestBase : public testing::Test {
// unique ids.
void VerifySstUniqueIds(const TablePropertiesCollection& props);
// Excludes kUnknown
extern const std::vector<Temperature> kKnownTemperatures;
Temperature RandomKnownTemperature();
} // namespace ROCKSDB_NAMESPACE
+3 -2
View File
@@ -428,8 +428,9 @@ bool StressTest::BuildOptionsTable() {
options_tbl.emplace(
"file_temperature_age_thresholds",
std::vector<std::string>{
"{{temperature=kWarm;age=10}:{temperature=kCold;age=50}:{"
"temperature=kIce;age=250}}",
"{{temperature=kWarm;age=10}:{temperature=kCool;age=30}:{"
"temperature=kCold;age=100}:{"
"temperature=kIce;age=300}}",
"{{temperature=kWarm;age=30}:{temperature=kCold;age=300}}",
"{{temperature=kCold;age=100}}", "{}"});
options_tbl.emplace(
+6
View File
@@ -80,6 +80,12 @@ inline void RecordIOStats(Statistics* stats, Temperature file_temperature,
RecordTick(stats, WARM_FILE_READ_BYTES, size);
RecordTick(stats, WARM_FILE_READ_COUNT, 1);
break;
case Temperature::kCool:
IOSTATS_ADD(file_io_stats_by_temperature.cool_file_bytes_read, size);
IOSTATS_ADD(file_io_stats_by_temperature.cool_file_read_count, 1);
RecordTick(stats, COOL_FILE_READ_BYTES, size);
RecordTick(stats, COOL_FILE_READ_COUNT, 1);
break;
case Temperature::kCold:
IOSTATS_ADD(file_io_stats_by_temperature.cold_file_bytes_read, size);
IOSTATS_ADD(file_io_stats_by_temperature.cold_file_read_count, 1);
+6
View File
@@ -32,6 +32,8 @@ struct FileIOByTemperature {
uint64_t hot_file_bytes_read;
// the number of bytes read to Temperature::kWarm file
uint64_t warm_file_bytes_read;
// the number of bytes read to Temperature::kCool file
uint64_t cool_file_bytes_read;
// the number of bytes read to Temperature::kCold file
uint64_t cold_file_bytes_read;
// the number of bytes read to Temperature::kIce file
@@ -40,6 +42,8 @@ struct FileIOByTemperature {
uint64_t hot_file_read_count;
// total number of reads to Temperature::kWarm file
uint64_t warm_file_read_count;
// total number of reads to Temperature::kCool file
uint64_t cool_file_read_count;
// total number of reads to Temperature::kCold file
uint64_t cold_file_read_count;
// total number of reads to Temperature::kIce file
@@ -48,10 +52,12 @@ struct FileIOByTemperature {
void Reset() {
hot_file_bytes_read = 0;
warm_file_bytes_read = 0;
cool_file_bytes_read = 0;
cold_file_bytes_read = 0;
ice_file_bytes_read = 0;
hot_file_read_count = 0;
warm_file_read_count = 0;
cool_file_read_count = 0;
cold_file_read_count = 0;
ice_file_read_count = 0;
}
+2
View File
@@ -443,10 +443,12 @@ enum Tickers : uint32_t {
// Tiered storage related statistics
HOT_FILE_READ_BYTES,
WARM_FILE_READ_BYTES,
COOL_FILE_READ_BYTES,
COLD_FILE_READ_BYTES,
ICE_FILE_READ_BYTES,
HOT_FILE_READ_COUNT,
WARM_FILE_READ_COUNT,
COOL_FILE_READ_COUNT,
COLD_FILE_READ_COUNT,
ICE_FILE_READ_COUNT,
+1
View File
@@ -118,6 +118,7 @@ enum class Temperature : uint8_t {
kUnknown = 0,
kHot = 0x04,
kWarm = 0x08,
kCool = 0x0A,
kCold = 0x0C,
kIce = 0x10,
// XXX: this is mis-named. It is instead an invalid temperature beyond the
+8
View File
@@ -5199,12 +5199,16 @@ class TickerTypeJni {
return -0x31;
case ROCKSDB_NAMESPACE::Tickers::WARM_FILE_READ_BYTES:
return -0x32;
case ROCKSDB_NAMESPACE::Tickers::COOL_FILE_READ_BYTES:
return -0x5B;
case ROCKSDB_NAMESPACE::Tickers::COLD_FILE_READ_BYTES:
return -0x33;
case ROCKSDB_NAMESPACE::Tickers::HOT_FILE_READ_COUNT:
return -0x34;
case ROCKSDB_NAMESPACE::Tickers::WARM_FILE_READ_COUNT:
return -0x35;
case ROCKSDB_NAMESPACE::Tickers::COOL_FILE_READ_COUNT:
return -0x5C;
case ROCKSDB_NAMESPACE::Tickers::COLD_FILE_READ_COUNT:
return -0x36;
case ROCKSDB_NAMESPACE::Tickers::LAST_LEVEL_READ_BYTES:
@@ -5664,12 +5668,16 @@ class TickerTypeJni {
return ROCKSDB_NAMESPACE::Tickers::HOT_FILE_READ_BYTES;
case -0x32:
return ROCKSDB_NAMESPACE::Tickers::WARM_FILE_READ_BYTES;
case -0x5B:
return ROCKSDB_NAMESPACE::Tickers::COOL_FILE_READ_BYTES;
case -0x33:
return ROCKSDB_NAMESPACE::Tickers::COLD_FILE_READ_BYTES;
case -0x34:
return ROCKSDB_NAMESPACE::Tickers::HOT_FILE_READ_COUNT;
case -0x35:
return ROCKSDB_NAMESPACE::Tickers::WARM_FILE_READ_COUNT;
case -0x5C:
return ROCKSDB_NAMESPACE::Tickers::COOL_FILE_READ_COUNT;
case -0x36:
return ROCKSDB_NAMESPACE::Tickers::COLD_FILE_READ_COUNT;
case -0x37:
@@ -764,10 +764,12 @@ public enum TickerType {
*/
HOT_FILE_READ_BYTES((byte) -0x31),
WARM_FILE_READ_BYTES((byte) -0x32),
COOL_FILE_READ_BYTES((byte) -0x5B),
COLD_FILE_READ_BYTES((byte) -0x33),
ICE_FILE_READ_BYTES((byte) -0x58),
HOT_FILE_READ_COUNT((byte) -0x34),
WARM_FILE_READ_COUNT((byte) -0x35),
COOL_FILE_READ_COUNT((byte) -0x5C),
COLD_FILE_READ_COUNT((byte) -0x36),
ICE_FILE_READ_COUNT((byte) -0x59),
+2
View File
@@ -65,9 +65,11 @@ std::string IOStatsContext::ToString(bool exclude_zero_counters) const {
IOSTATS_CONTEXT_OUTPUT(cpu_read_nanos);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.hot_file_bytes_read);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.warm_file_bytes_read);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.cool_file_bytes_read);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.cold_file_bytes_read);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.hot_file_read_count);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.warm_file_read_count);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.cool_file_read_count);
IOSTATS_CONTEXT_OUTPUT(file_io_stats_by_temperature.cold_file_read_count);
std::string str = ss.str();
str.erase(str.find_last_not_of(", ") + 1);
+2
View File
@@ -226,10 +226,12 @@ const std::vector<std::pair<Tickers, std::string>> TickersNameMap = {
{REMOTE_COMPACT_WRITE_BYTES, "rocksdb.remote.compact.write.bytes"},
{HOT_FILE_READ_BYTES, "rocksdb.hot.file.read.bytes"},
{WARM_FILE_READ_BYTES, "rocksdb.warm.file.read.bytes"},
{COOL_FILE_READ_BYTES, "rocksdb.cool.file.read.bytes"},
{COLD_FILE_READ_BYTES, "rocksdb.cold.file.read.bytes"},
{ICE_FILE_READ_BYTES, "rocksdb.ice.file.read.bytes"},
{HOT_FILE_READ_COUNT, "rocksdb.hot.file.read.count"},
{WARM_FILE_READ_COUNT, "rocksdb.warm.file.read.count"},
{COOL_FILE_READ_COUNT, "rocksdb.cool.file.read.count"},
{COLD_FILE_READ_COUNT, "rocksdb.cold.file.read.count"},
{ICE_FILE_READ_COUNT, "rocksdb.ice.file.read.count"},
{LAST_LEVEL_READ_BYTES, "rocksdb.last.level.read.bytes"},
+6 -10
View File
@@ -367,11 +367,9 @@ std::map<CompactionStopStyle, std::string>
{kCompactionStopStyleTotalSize, "kCompactionStopStyleTotalSize"}};
std::map<Temperature, std::string> OptionsHelper::temperature_to_string = {
{Temperature::kUnknown, "kUnknown"},
{Temperature::kHot, "kHot"},
{Temperature::kWarm, "kWarm"},
{Temperature::kCold, "kCold"},
{Temperature::kIce, "kIce"}};
{Temperature::kUnknown, "kUnknown"}, {Temperature::kHot, "kHot"},
{Temperature::kWarm, "kWarm"}, {Temperature::kCool, "kCool"},
{Temperature::kCold, "kCold"}, {Temperature::kIce, "kIce"}};
std::unordered_map<std::string, ChecksumType>
OptionsHelper::checksum_type_string_map = {{"kNoChecksum", kNoChecksum},
@@ -966,11 +964,9 @@ std::unordered_map<std::string, CompactionStopStyle>
std::unordered_map<std::string, Temperature>
OptionsHelper::temperature_string_map = {
{"kUnknown", Temperature::kUnknown},
{"kHot", Temperature::kHot},
{"kWarm", Temperature::kWarm},
{"kCold", Temperature::kCold},
{"kIce", Temperature::kIce}};
{"kUnknown", Temperature::kUnknown}, {"kHot", Temperature::kHot},
{"kWarm", Temperature::kWarm}, {"kCool", Temperature::kCool},
{"kCold", Temperature::kCold}, {"kIce", Temperature::kIce}};
std::unordered_map<std::string, PrepopulateBlobCache>
OptionsHelper::prepopulate_blob_cache_string_map = {
+4 -4
View File
@@ -377,13 +377,13 @@ default_params = {
"enable_custom_split_merge": lambda: random.choice([0, 1]),
"adm_policy": lambda: random.choice([0, 1, 2, 3]),
"last_level_temperature": lambda: random.choice(
["kUnknown", "kHot", "kWarm", "kCold", "kIce"]
["kUnknown", "kHot", "kWarm", "kCool", "kCold", "kIce"]
),
"default_write_temperature": lambda: random.choice(
["kUnknown", "kHot", "kWarm", "kCold", "kIce"]
["kUnknown", "kHot", "kWarm", "kCool", "kCold", "kIce"]
),
"default_temperature": lambda: random.choice(
["kUnknown", "kHot", "kWarm", "kCold", "kIce"]
["kUnknown", "kHot", "kWarm", "kCool", "kCold", "kIce"]
),
# TODO(hx235): enable `enable_memtable_insert_with_hint_prefix_extractor`
# after fixing the surfaced issue with delete range
@@ -696,7 +696,7 @@ tiered_params = {
# For FIFO compaction (ignored otherwise)
"file_temperature_age_thresholds": lambda: random.choice(
[
"{{temperature=kWarm;age=10}:{temperature=kCold;age=50}:{temperature=kIce;age=250}}",
"{{temperature=kWarm;age=10}:{temperature=kCool;age=30}:{temperature=kCold;age=100}:{temperature=kIce;age=300}}",
"{{temperature=kWarm;age=30}:{temperature=kCold;age=300}}",
"{{temperature=kCold;age=100}}",
]