eqemu-server/zone/cli/benchmark_databuckets.cpp
Chris Miles 49cf97ae9c
[Databuckets] Add Zone Scoped Databuckets (#4690)
* [Databuckets] Add Zone Scoped Databuckets

* Add database indexes

* Update database_update_manifest.cpp

* Shutdown fix

* Testing

* Perf boost

* Revert "Perf boost"

This reverts commit 55d3e507d30719fc3f631806a4c5d09bc65430a1.

* Update data_bucket.cpp
2025-02-18 00:14:49 -06:00

278 lines
9.7 KiB
C++

#include <chrono>
#include <iostream>
#include <random>
#include "../../common/http/httplib.h"
#include "../../common/eqemu_logsys.h"
#include "../sidecar_api/sidecar_api.h"
#include "../../common/platform.h"
#include "../data_bucket.h"
#include "../zonedb.h"
#include "../../common/repositories/data_buckets_repository.h"
void RunBenchmarkCycle(uint64_t target_rows)
{
const size_t OPERATIONS_PER_TEST = 5000;
const std::string test_key_prefix = "test_key_";
std::cout << Strings::Repeat("-", 70) << "\n";
std::cout << "📊 Running Benchmark at " << Strings::Commify(target_rows) << " Rows...\n";
std::cout << Strings::Repeat("-", 70) << "\n";
// 🧹 **Purge `test_key_*` Keys Before Each Run**
std::cout << "🧹 Purging test keys (`test_key_*`)...\n";
auto purge_start = std::chrono::high_resolution_clock::now();
DataBucketsRepository::DeleteWhere(database, "`key` LIKE '" + test_key_prefix + "%'");
auto purge_end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> purge_time = purge_end - purge_start;
std::cout << "✅ Purged test keys in " << purge_time.count() << " seconds.\n";
// 📊 **Ensure the Table Contains At Least `target_rows`**
auto populate_start = std::chrono::high_resolution_clock::now();
uint64_t current_count = DataBucketsRepository::Count(database);
if (current_count < target_rows) {
std::cout << "📌 Populating table to " << Strings::Commify(target_rows) << " rows...\n";
std::mt19937 rng(std::random_device{}());
std::uniform_int_distribution<int> entity_type(1, 5);
std::uniform_int_distribution<int> id_dist(1, 1000000);
std::uniform_int_distribution<int> expiry_dist(0, 86400 * 30); // Expiry up to 30 days
while (current_count < target_rows) {
std::vector<DataBucketsRepository::DataBuckets> batch;
for (size_t i = 0; i < 100000; ++i) {
if (i > target_rows - current_count) {
break;
}
int entity_choice = entity_type(rng);
int entity_id = id_dist(rng);
std::string key = "test_key_" + std::to_string(current_count + i);
std::string value = "value_" + std::to_string(current_count + i);
int expires = static_cast<int>(std::time(nullptr)) + expiry_dist(rng);
DataBucketsRepository::DataBuckets e{};
e.key_ = key;
e.value = value;
e.expires = expires;
e.account_id = (entity_choice == 1) ? entity_id : 0;
e.character_id = (entity_choice == 2) ? entity_id : 0;
e.npc_id = (entity_choice == 3) ? entity_id : 0;
e.bot_id = (entity_choice == 4) ? entity_id : 0;
e.zone_id = (entity_choice == 5) ? entity_id : 0;
e.instance_id = (entity_choice == 5) ? entity_id : 0;
batch.emplace_back(e);
}
DataBucketsRepository::InsertMany(database, batch);
current_count += batch.size();
}
}
else {
std::cout << "✅ Table already has " << current_count << " rows, proceeding with benchmark.\n";
}
auto populate_end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> populate_time = populate_end - populate_start;
std::cout << "✅ Populated table in " << populate_time.count() << " seconds.\n";
std::mt19937 rng(std::random_device{}());
std::uniform_int_distribution<int> id_dist(1, 1000);
// 🚀 **Measure Insert Performance**
std::vector<DataBucketKey> inserted_keys = {};
auto insert_start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < OPERATIONS_PER_TEST; ++i) {
std::string key = test_key_prefix + std::to_string(current_count + i);
std::string value = "value_" + std::to_string(current_count + i);
int expires = static_cast<int>(std::time(nullptr)) + 3600;
DataBucketKey e{
.key = key,
.value = value,
.expires = std::to_string(expires),
.account_id = 0,
.character_id = 0,
.npc_id = 0,
.bot_id = 0
};
// randomly set account_id, character_id, npc_id, or bot_id
switch (i % 4) {
case 0:
e.account_id = id_dist(rng);
break;
case 1:
e.character_id = id_dist(rng);
break;
case 2:
e.npc_id = id_dist(rng);
break;
case 3:
e.bot_id = id_dist(rng);
break;
case 4:
int entity_choice = id_dist(rng);
e.zone_id = entity_choice;
e.instance_id = entity_choice;
break;
}
DataBucket::SetData(e);
inserted_keys.emplace_back(e);
}
auto insert_end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> insert_time = insert_end - insert_start;
std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) << " inserts in " << insert_time.count()
<< " seconds. (Individual Insert Performance)\n";
// ✏️ **Measure Update Performance Using DataBucket**
auto update_start = std::chrono::high_resolution_clock::now();
for (auto &key: inserted_keys) {
// 🔍 Retrieve existing bucket using scoped `GetData`
auto e = DataBucket::GetData(key);
if (e.id > 0) {
// create a new key object with the updated values
DataBucketKey bucket_entry_key{
.key = e.key_,
.value = "some_new_value",
.expires = std::to_string(e.expires),
.account_id = e.account_id,
.character_id = e.character_id,
.npc_id = e.npc_id,
.bot_id = e.bot_id,
.zone_id = e.zone_id,
.instance_id = e.instance_id
};
// 🔄 Update using DataBucket class
DataBucket::SetData(bucket_entry_key);
}
}
auto update_end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> update_time = update_end - update_start;
std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) << " updates in " << update_time.count()
<< " seconds. (Scoped Update Performance)\n";
// 🔍 **Measure Cached Read Performance**
auto read_cached_start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < OPERATIONS_PER_TEST; ++i) {
std::string key = test_key_prefix + std::to_string(current_count + i);
DataBucketKey k{
.key = key,
.account_id = 0,
.character_id = 0,
.npc_id = 0,
.bot_id = 0,
.zone_id = 0,
.instance_id = 0
};
// randomly set account_id, character_id, npc_id, or bot_id
switch (i % 4) {
case 0:
k.account_id = id_dist(rng);
break;
case 1:
k.character_id = id_dist(rng);
break;
case 2:
k.npc_id = id_dist(rng);
break;
case 3:
k.bot_id = id_dist(rng);
break;
case 4:
int entity_choice = id_dist(rng);
k.zone_id = entity_choice;
k.instance_id = entity_choice;
}
DataBucket::GetData(key);
}
auto read_cached_end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> read_cached_time = read_cached_end - read_cached_start;
std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) << " cached reads in "
<< read_cached_time.count() << " seconds. (DataBucket::GetData)\n";
// 🔍 **Measure Non-Cached Read Performance (Direct Query)**
auto read_uncached_start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < OPERATIONS_PER_TEST; ++i) {
std::string key = test_key_prefix + std::to_string(current_count + i);
DataBucketsRepository::GetWhere(database, "`key` = '" + key + "'");
}
auto read_uncached_end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> read_uncached_time = read_uncached_end - read_uncached_start;
std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) << " non-cached reads in "
<< read_uncached_time.count() << " seconds. (DataBucketsRepository::GetWhere)\n";
// 🗑️ **Measure Delete Performance**
auto delete_start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < OPERATIONS_PER_TEST; ++i) {
std::string key = test_key_prefix + std::to_string(current_count + i);
DataBucketKey k{
.key = key,
.account_id = 0,
.character_id = 0,
.npc_id = 0,
.bot_id = 0,
.zone_id = 0,
.instance_id = 0
};
// randomly set account_id, character_id, npc_id, or bot_id
switch (i % 4) {
case 0:
k.account_id = id_dist(rng);
break;
case 1:
k.character_id = id_dist(rng);
break;
case 2:
k.npc_id = id_dist(rng);
break;
case 3:
k.bot_id = id_dist(rng);
break;
case 4:
int entity_choice = id_dist(rng);
k.zone_id = entity_choice;
k.instance_id = entity_choice;
}
DataBucket::DeleteData(k);
}
auto delete_end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> delete_time = delete_end - delete_start;
std::cout << "✅ Completed " << Strings::Commify(OPERATIONS_PER_TEST) << " deletes in " << delete_time.count()
<< " seconds.\n";
}
void ZoneCLI::BenchmarkDatabuckets(int argc, char **argv, argh::parser &cmd, std::string &description)
{
description = "Benchmark individual reads/writes/deletes in data_buckets at different table sizes.";
if (cmd[{"-h", "--help"}]) {
std::cout << "Usage: BenchmarkDatabuckets\n";
return;
}
if (std::getenv("DEBUG")) {
LogSys.SetDatabase(&database)->LoadLogDatabaseSettings();
}
auto start_time = std::chrono::high_resolution_clock::now();
std::vector<uint64_t> benchmark_sizes = {10000, 100000, 1000000};
for (auto size: benchmark_sizes) {
RunBenchmarkCycle(size);
}
// 🚀 **Total Benchmark Time**
auto end_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> total_elapsed = end_time - start_time;
std::cout << "\n🚀 Total Benchmark Time: " << total_elapsed.count() << " seconds\n";
}