Ver código fonte

hashing, makefile, testing

Danilo Fragoso 10 meses atrás
pai
commit
97e6cb9dd2
6 arquivos alterados com 1093 adições e 39 exclusões
  1. 0 1
      .gitignore
  2. 108 0
      hashing.zig
  3. 13 0
      makefile
  4. 67 38
      storage.zig
  5. 259 0
      tools/test_1m.js
  6. 646 0
      tools/test_nov.js

+ 0 - 1
.gitignore

@@ -1,3 +1,2 @@
 pizzakv
 main
-tools/

+ 108 - 0
hashing.zig

@@ -0,0 +1,108 @@
+const std = @import("std");
+
+pub fn hashKey(k: []const u8) u32 {
+    return murmur3(k);
+}
+
+pub fn xoramasrosas(k: []const u8) u32 {
+    var hash: u32 = 17 * 22;
+    const x = "xoramasrosas";
+
+    for (k, 0..) |char, i| {
+        hash = hash +% (char ^ x[i % 12]) << 12;
+    }
+
+    return hash;
+}
+
+pub fn djb2(key: []const u8) u32 {
+    var hash: u32 = 5381;
+
+    for (key) |c| {
+        hash = ((hash << 5) +% hash) +% c;
+    }
+
+    return hash;
+}
+
+pub fn murmur3(key: []const u8) u32 {
+    const seed: u32 = 0;
+    var hash: u32 = seed;
+
+    const c1: u32 = 0xcc9e2d51;
+    const c2: u32 = 0x1b873593;
+
+    var i: usize = 0;
+    while (i + 4 <= key.len) : (i += 4) {
+        var k: u32 = @as(u32, key[i]) |
+            (@as(u32, key[i + 1]) << 8) |
+            (@as(u32, key[i + 2]) << 16) |
+            (@as(u32, key[i + 3]) << 24);
+
+        k *%= c1;
+        k = (k << 15) | (k >> 17);
+        k *%= c2;
+
+        hash ^= k;
+        hash = (hash << 13) | (hash >> 19);
+        hash = hash *% 5 +% 0xe6546b64;
+    }
+
+    var k: u32 = 0;
+    const remaining = key.len - i;
+    if (remaining >= 3) k ^= @as(u32, key[i + 2]) << 16;
+    if (remaining >= 2) k ^= @as(u32, key[i + 1]) << 8;
+    if (remaining >= 1) {
+        k ^= @as(u32, key[i]);
+        k *%= c1;
+        k = (k << 15) | (k >> 17);
+        k *%= c2;
+        hash ^= k;
+    }
+
+    hash ^= @as(u32, @intCast(key.len));
+    hash ^= hash >> 16;
+    hash *%= 0x85ebca6b;
+    hash ^= hash >> 13;
+    hash *%= 0xc2b2ae35;
+    hash ^= hash >> 16;
+
+    return hash;
+}
+
+pub fn xxhash32(key: []const u8) u32 {
+    const PRIME1: u32 = 2654435761;
+    const PRIME2: u32 = 2246822519;
+    const PRIME3: u32 = 3266489917;
+    const PRIME4: u32 = 668265263;
+    const PRIME5: u32 = 374761393;
+
+    var hash: u32 = PRIME5 +% @as(u32, @intCast(key.len));
+
+    var i: usize = 0;
+    while (i + 4 <= key.len) : (i += 4) {
+        const k: u32 = @as(u32, key[i]) |
+            (@as(u32, key[i + 1]) << 8) |
+            (@as(u32, key[i + 2]) << 16) |
+            (@as(u32, key[i + 3]) << 24);
+        hash +%= k *% PRIME3;
+        hash = ((hash << 17) | (hash >> 15)) *% PRIME4;
+    }
+
+    while (i < key.len) : (i += 1) {
+        hash +%= @as(u32, key[i]) *% PRIME5;
+        hash = ((hash << 11) | (hash >> 21)) *% PRIME1;
+    }
+
+    hash ^= hash >> 15;
+    hash *%= PRIME2;
+    hash ^= hash >> 13;
+    hash *%= PRIME3;
+    hash ^= hash >> 16;
+
+    return hash;
+}
+
+pub fn wyhash(key: []const u8) u32 {
+    return @as(u32, @truncate(std.hash.Wyhash.hash(0, key)));
+}

+ 13 - 0
makefile

@@ -0,0 +1,13 @@
+default: build
+
+build:
+	zig build-exe main.zig -O ReleaseFast --name pizzakv
+
+install: build
+	mv pizzakv /usr/local/bin/pizzakv
+
+clean:
+	rm -f pizzakv
+
+test:
+	node tools/test_nov.js

+ 67 - 38
storage.zig

@@ -1,64 +1,93 @@
 const std = @import("std");
+const hashing = @import("hashing.zig");
+
+const MAX_RECORDS = 10_000_000;
+const Entry = struct {
+    key: []const u8,
+    value: []const u8,
+    next: ?*Entry,
+};
+var buf: [MAX_RECORDS]?*Entry = undefined;
 
-const backend_size = 1024 * 1024 * 100;
-var buf: [backend_size][]const u8 = undefined;
 var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
 const allocator = arena.allocator();
 
 const EMPTY = "";
+var mutex: std.Thread.Mutex = .{};
 
-pub fn hashKey(k: []const u8) u32 {
-    return djb2(k);
-    //return xoramasrosas(k);
-}
-
-pub fn djb2(key: []const u8) u32 {
-    var hash: u32 = 5381;
-
-    for (key) |c| {
-        hash = ((hash << 5) +% hash) +% c;
-    }
+pub fn write(key: []const u8, value: []const u8) bool {
+    mutex.lock();
+    defer mutex.unlock();
 
-    return hash;
-}
+    const hash = hashing.hashKey(key);
+    const index = hash % buf.len;
 
-pub fn xoramasrosas(k: []const u8) u32 {
-    var hash: u32 = 17 * 22;
-    const x = "xoramasrosas";
+    var current = buf[index];
+    while (current) |entry| {
+        if (std.mem.eql(u8, entry.key, key)) {
+            allocator.free(entry.value);
+            entry.value = allocator.dupe(u8, value) catch return false;
+            return true;
+        }
 
-    for (k, 0..) |char, i| {
-        hash = hash +% (char ^ x[i % 12]) << 12;
+        current = entry.next;
     }
 
-    return hash;
-}
-
-pub fn write(key: []const u8, value: []const u8) bool {
-    const valueCopy = allocator.dupe(u8, value) catch {
-        std.debug.print("Failed to duplicate value for key: {s}\n", .{key});
-        return false;
+    const newEntry = allocator.create(Entry) catch return false;
+    errdefer allocator.destroy(newEntry);
+    newEntry.* = Entry{
+        .key = allocator.dupe(u8, key) catch return false,
+        .value = allocator.dupe(u8, value) catch return false,
+        .next = buf[index],
     };
 
-    const hash = hashKey(key);
-    buf[hash % buf.len] = valueCopy;
+    buf[index] = newEntry;
     return true;
 }
 
 pub fn read(key: []const u8) ?[]const u8 {
-    const hash = hashKey(key);
-    if (buf[hash % buf.len].len == 0) {
-        return null;
+    mutex.lock();
+    defer mutex.unlock();
+
+    const hash = hashing.hashKey(key);
+    var current = buf[hash % buf.len];
+    while (current) |entry| {
+        if (std.mem.eql(u8, entry.key, key)) {
+            return entry.value;
+        }
+        current = entry.next;
     }
 
-    return buf[hash % buf.len];
+    return null;
 }
 
 pub fn delete(key: []const u8) bool {
-    const hash = hashKey(key);
-    if (hash % buf.len >= buf.len) {
-        return false;
+    mutex.lock();
+    defer mutex.unlock();
+
+    const hash = hashing.hashKey(key);
+    const index = hash % buf.len;
+
+    var current = buf[index];
+    var prev: ?*Entry = null;
+
+    while (current) |entry| {
+        if (std.mem.eql(u8, entry.key, key)) {
+            if (prev) |p| {
+                p.next = entry.next;
+            } else {
+                buf[index] = entry.next;
+            }
+
+            allocator.free(entry.key);
+            allocator.free(entry.value);
+            allocator.destroy(entry);
+            return true;
+        }
+
+        prev = entry;
+        current = entry.next;
     }
 
-    buf[hash % buf.len] = EMPTY;
-    return true;
+    return false;
 }

+ 259 - 0
tools/test_1m.js

@@ -0,0 +1,259 @@
+const net = require('net');
+
+class KVClient {
+  constructor(host = 'localhost', port = 8080) {
+    this.host = host;
+    this.port = port;
+    this.client = null;
+  }
+
+  connect() {
+    return new Promise((resolve, reject) => {
+      this.client = net.createConnection({ host: this.host, port: this.port }, () => {
+        console.log('Connected to KV database');
+        resolve();
+      });
+
+      this.client.on('error', (err) => {
+        reject(err);
+      });
+    });
+  }
+
+  sendCommand(command) {
+    return new Promise((resolve, reject) => {
+      let response = '';
+
+      const dataHandler = (data) => {
+        response += data.toString();
+        // Assuming responses end with \r
+        if (response.includes('\r')) {
+          this.client.removeListener('data', dataHandler);
+          resolve(response.replace(/\r/g, '').trim());
+        }
+      };
+
+      this.client.on('data', dataHandler);
+
+      this.client.write(command + '\r', (err) => {
+        if (err) {
+          this.client.removeListener('data', dataHandler);
+          reject(err);
+        }
+      });
+
+      // Timeout after 5 seconds
+      setTimeout(() => {
+        this.client.removeListener('data', dataHandler);
+        reject(new Error('Command timeout'));
+      }, 5000);
+    });
+  }
+
+  async write(key, value) {
+    return await this.sendCommand(`write ${key}|${value}`);
+  }
+
+  async read(key) {
+    return await this.sendCommand(`read ${key}`);
+  }
+
+  close() {
+    if (this.client) {
+      this.client.end();
+    }
+  }
+}
+
+async function testBatch(client, batchSize, batchNum, allResults) {
+  console.log(`\n${'='.repeat(60)}`);
+  console.log(`BATCH ${batchNum}: Writing and verifying ${batchSize.toLocaleString()} entries`);
+  console.log('='.repeat(60));
+
+  const batchData = new Map();
+  const batchResults = {
+    batchSize,
+    batchNum,
+    successfulWrites: 0,
+    failedWrites: 0,
+    successfulReads: 0,
+    failedReads: 0,
+    mismatches: 0,
+    errors: []
+  };
+
+  // Write phase
+  console.log(`\nWriting ${batchSize.toLocaleString()} entries...`);
+  const startWrite = Date.now();
+  
+  for (let i = 0; i < batchSize; i++) {
+    const key = `key_batch${batchNum}_${i}`;
+    const value = `value_${batchNum}_${i}_${Math.random().toString(36).substring(7)}`;
+    batchData.set(key, value);
+    
+    try {
+      await client.write(key, value);
+      batchResults.successfulWrites++;
+    } catch (err) {
+      batchResults.failedWrites++;
+      batchResults.errors.push({ operation: 'write', key, error: err.message });
+    }
+
+    if ((i + 1) % Math.max(1, Math.floor(batchSize / 10)) === 0) {
+      const progress = ((i + 1) / batchSize * 100).toFixed(1);
+      process.stdout.write(`\r  Progress: ${(i + 1).toLocaleString()}/${batchSize.toLocaleString()} (${progress}%)`);
+    }
+  }
+  
+  const writeDuration = Date.now() - startWrite;
+  console.log(`\n  Completed in ${writeDuration}ms (${(writeDuration / 1000).toFixed(2)}s) - ${(batchSize / (writeDuration / 1000)).toFixed(2)} writes/sec`);
+
+  // Read and verify phase
+  console.log(`\nVerifying ${batchSize.toLocaleString()} entries...`);
+  const startRead = Date.now();
+  
+  let readCount = 0;
+  for (const [key, expectedValue] of batchData) {
+    try {
+      const response = await client.read(key);
+      readCount++;
+      
+      // Parse response (adjust based on your server's response format)
+      const actualValue = response.includes('|') ? response.split('|')[1] : response;
+      
+      if (actualValue === expectedValue) {
+        batchResults.successfulReads++;
+      } else {
+        batchResults.mismatches++;
+        batchResults.errors.push({
+          operation: 'read',
+          key,
+          expected: expectedValue,
+          actual: actualValue
+        });
+      }
+      
+      if (readCount % Math.max(1, Math.floor(batchSize / 10)) === 0) {
+        const progress = (readCount / batchSize * 100).toFixed(1);
+        process.stdout.write(`\r  Progress: ${readCount.toLocaleString()}/${batchSize.toLocaleString()} (${progress}%)`);
+      }
+    } catch (err) {
+      batchResults.failedReads++;
+      batchResults.errors.push({ operation: 'read', key, error: err.message });
+    }
+  }
+  
+  const readDuration = Date.now() - startRead;
+  console.log(`\n  Completed in ${readDuration}ms (${(readDuration / 1000).toFixed(2)}s) - ${(batchSize / (readDuration / 1000)).toFixed(2)} reads/sec`);
+
+  // Batch summary
+  const batchSuccess = batchResults.failedWrites === 0 && 
+                       batchResults.failedReads === 0 && 
+                       batchResults.mismatches === 0;
+  
+  console.log(`\nBatch ${batchNum} Results:`);
+  console.log(`  Writes: ${batchResults.successfulWrites}/${batchSize} successful`);
+  console.log(`  Reads: ${batchResults.successfulReads}/${batchSize} successful`);
+  console.log(`  Status: ${batchSuccess ? '✓ PASSED' : '✗ FAILED'}`);
+
+  if (batchResults.errors.length > 0) {
+    console.log(`  Errors: ${batchResults.errors.length} (showing first 5):`);
+    batchResults.errors.slice(0, 5).forEach((err, idx) => {
+      console.log(`    ${idx + 1}. ${err.operation.toUpperCase()} ${err.key}: ${err.error || 'Value mismatch'}`);
+    });
+  }
+
+  allResults.push(batchResults);
+  return batchData;
+}
+
+async function testKVDatabase() {
+  const client = new KVClient('localhost', 8080);
+  const allResults = [];
+  const allData = new Map();
+
+  // Batch sizes: 1K, 10K, 100K, 1M
+  const batches = [
+    { size: 1000, label: '1K' },
+    { size: 10000, label: '10K' },
+    { size: 100000, label: '100K' },
+    { size: 1000000, label: '1M' }
+  ];
+
+  try {
+    await client.connect();
+
+    const overallStart = Date.now();
+
+    for (let i = 0; i < batches.length; i++) {
+      const batchData = await testBatch(client, batches[i].size, i + 1, allResults);
+      // Store all data for potential final verification
+      for (const [key, value] of batchData) {
+        allData.set(key, value);
+      }
+    }
+
+    const overallDuration = Date.now() - overallStart;
+
+    // Final comprehensive report
+    console.log('\n\n' + '='.repeat(60));
+    console.log('FINAL COMPREHENSIVE REPORT');
+    console.log('='.repeat(60));
+
+    let totalWrites = 0, successfulWrites = 0, failedWrites = 0;
+    let totalReads = 0, successfulReads = 0, failedReads = 0, totalMismatches = 0;
+    let totalErrors = 0;
+
+    console.log('\nPer-Batch Summary:');
+    allResults.forEach((batch) => {
+      totalWrites += batch.batchSize;
+      successfulWrites += batch.successfulWrites;
+      failedWrites += batch.failedWrites;
+      successfulReads += batch.successfulReads;
+      failedReads += batch.failedReads;
+      totalMismatches += batch.mismatches;
+      totalErrors += batch.errors.length;
+
+      const batchSuccess = batch.failedWrites === 0 && batch.failedReads === 0 && batch.mismatches === 0;
+      console.log(`  Batch ${batch.batchNum} (${batch.batchSize.toLocaleString()}): ${batchSuccess ? '✓ PASSED' : '✗ FAILED'}`);
+    });
+
+    console.log(`\nOverall Statistics:`);
+    console.log(`  Total entries: ${allData.size.toLocaleString()}`);
+    console.log(`  Total time: ${overallDuration}ms (${(overallDuration / 1000).toFixed(2)}s)`);
+    console.log(`  Average throughput: ${(totalWrites / (overallDuration / 1000)).toFixed(2)} operations/sec`);
+
+    console.log(`\nWrite Operations:`);
+    console.log(`  Total: ${totalWrites.toLocaleString()}`);
+    console.log(`  Successful: ${successfulWrites.toLocaleString()}`);
+    console.log(`  Failed: ${failedWrites.toLocaleString()}`);
+    console.log(`  Success rate: ${((successfulWrites / totalWrites) * 100).toFixed(2)}%`);
+
+    console.log(`\nRead Operations:`);
+    console.log(`  Total: ${totalReads.toLocaleString()}`);
+    console.log(`  Successful: ${successfulReads.toLocaleString()}`);
+    console.log(`  Failed: ${failedReads.toLocaleString()}`);
+    console.log(`  Mismatches: ${totalMismatches.toLocaleString()}`);
+    console.log(`  Success rate: ${((successfulReads / totalReads) * 100).toFixed(2)}%`);
+
+    const overallSuccess = failedWrites === 0 && failedReads === 0 && totalMismatches === 0;
+    console.log(`\n${'='.repeat(60)}`);
+    console.log(`FINAL RESULT: ${overallSuccess ? '✓ ALL TESTS PASSED!' : '✗ SOME TESTS FAILED'}`);
+    console.log('='.repeat(60));
+
+    if (totalErrors > 0) {
+      console.log(`\nTotal errors across all batches: ${totalErrors}`);
+    }
+
+  } catch (err) {
+    console.error('\nConnection error:', err.message);
+  } finally {
+    client.close();
+  }
+}
+
+// Run the test
+console.log('Starting KV Database Progressive Load Test');
+console.log('Testing batches: 1K → 10K → 100K → 1M entries\n');
+
+testKVDatabase().catch(console.error);

+ 646 - 0
tools/test_nov.js

@@ -0,0 +1,646 @@
+const net = require('net');
+
+class KVDBClient {
+  constructor(host = 'localhost', port = 8080) {
+    this.host = host;
+    this.port = port;
+    this.socket = null;
+    this.connected = false;
+  }
+
+  async connect() {
+    if (this.connected) return;
+    
+    return new Promise((resolve, reject) => {
+      this.socket = new net.Socket();
+      this.socket.setKeepAlive(true);
+      this.socket.setNoDelay(true);
+      
+      const timeout = setTimeout(() => {
+        reject(new Error('Connection timeout'));
+      }, 5000);
+      
+      this.socket.connect(this.port, this.host, () => {
+        clearTimeout(timeout);
+        this.connected = true;
+        resolve();
+      });
+      
+      this.socket.once('error', (err) => {
+        clearTimeout(timeout);
+        this.connected = false;
+        reject(err);
+      });
+    });
+  }
+
+  disconnect() {
+    if (this.socket) {
+      this.socket.destroy();
+      this.socket = null;
+      this.connected = false;
+    }
+  }
+
+  sendCommand(command, debug = false) {
+    return new Promise(async (resolve, reject) => {
+      if (!this.socket) {
+        try {
+          await this.connect();
+        } catch (err) {
+          return reject(err);
+        }
+      }
+      
+      let response = '';
+
+      const dataHandler = (data) => {
+        response += data.toString();
+        
+        if (response.includes('\r')) {
+          this.socket.removeListener('data', dataHandler);
+          clearTimeout(timeout);
+          const result = response.replace(/\r/g, '').trim();
+          
+          if (debug) {
+            console.log(`[DEBUG] Recebido: ${result}`);
+          }
+          
+          resolve(result);
+        }
+      };
+
+      this.socket.on('data', dataHandler);
+
+      const timeout = setTimeout(() => {
+        this.socket.removeListener('data', dataHandler);
+        reject(new Error(`Timeout após 5s`));
+      }, 5000);
+
+      if (debug) {
+        console.log(`[DEBUG] Enviando: ${command.replace(/\r/g, '\\r')}`);
+      }
+      
+      this.socket.write(command, (err) => {
+        if (err) {
+          this.socket.removeListener('data', dataHandler);
+          clearTimeout(timeout);
+          reject(err);
+        }
+      });
+    });
+  }
+
+  async write(key, value) {
+    return this.sendCommand(`write ${key}|${value}\r`);
+  }
+
+  async read(key) {
+    return this.sendCommand(`read ${key}\r`);
+  }
+
+  async delete(key) {
+    return this.sendCommand(`delete ${key}\r`);
+  }
+
+  async status() {
+    return this.sendCommand(`status\r`);
+  }
+
+  async keys() {
+    return this.sendCommand(`keys\r`);
+  }
+
+  async reads(prefix) {
+    return this.sendCommand(`reads ${prefix}\r`);
+  }
+}
+
+function generateValue(size) {
+  return 'x'.repeat(size);
+}
+
+function generateKey(prefix, id) {
+  return `${prefix}:${id}`;
+}
+
+async function testBasicFeatures() {
+  console.log('\n=== TESTE 1: FUNCIONALIDADES BÁSICAS ===\n');
+  const client = new KVDBClient();
+  const features = {
+    write: true,
+    read: true,
+    delete: true,
+    keys: true,
+    reads: true,
+    status: true
+  };
+
+  try {
+    console.log('Testando CONEXÃO...');
+    const status = await client.status();
+    console.log(`✓ Conexão OK. Status: ${status}\n`);
+
+    console.log('Testando WRITE...');
+    await client.write('test:1', 'valor1');
+    console.log(`✓ Write: success`);
+
+    console.log('\nTestando READ...');
+    const readRes = await client.read('test:1');
+    console.log(`✓ Read: ${readRes}`);
+
+    console.log('\nTestando WRITE múltiplas chaves...');
+    await client.write('test:2', 'valor2');
+    await client.write('test:3', 'valor3');
+    await client.write('other:1', 'outro');
+    console.log(`✓ Múltiplas escritas concluídas`);
+
+    console.log('\nTestando KEYS...');
+    try {
+      const keysRes = await client.keys();
+      if (keysRes === 'error') {
+        throw new Error('retornou error');
+      }
+      const keysList = keysRes.split('\n').filter(k => k.trim());
+      console.log(`✓ Keys (${keysList.length} chaves): ${keysList.slice(0, 5).join(', ')}${keysList.length > 5 ? '...' : ''}`);
+    } catch (err) {
+      features.keys = false;
+      console.log(`⊘ KEYS não suportado (${err.message}) - será ignorado nos testes`);
+    }
+
+    console.log('\nTestando READS com prefixo "test"...');
+    try {
+      const readsRes = await client.reads('test');
+      if (readsRes === 'error') {
+        throw new Error('retornou error');
+      }
+      const readsList = readsRes.split('\n').filter(v => v.trim());
+      console.log(`✓ Reads (${readsList.length} valores): ${readsList.slice(0, 3).join(', ')}${readsList.length > 3 ? '...' : ''}`);
+    } catch (err) {
+      features.reads = false;
+      console.log(`⊘ READS não suportado (${err.message}) - será ignorado nos testes`);
+    }
+
+    console.log('\nTestando DELETE...');
+    try {
+      const delRes = await client.delete('test:1');
+      if (delRes === 'error') {
+        throw new Error('retornou error');
+      }
+      console.log(`✓ Delete: ${delRes}`);
+
+      console.log('Verificando se foi deletado...');
+      const delReadRes = await client.read('test:1');
+      if (delReadRes !== 'error') {
+        throw new Error(`chave ainda existe com valor "${delReadRes}"`);
+      }
+      console.log(`✓ Chave foi deletada corretamente`);
+    } catch (err) {
+      features.delete = false;
+      console.log(`⊘ DELETE não suportado (${err.message}) - será ignorado nos testes`);
+    }
+
+    console.log('\nTestando DELETE em chave inexistente...');
+    if (features.delete) {
+      const delNonExist = await client.delete('nao:existe');
+      if (delNonExist !== 'error') {
+        console.log(`⚠ DELETE deveria retornar error para chave inexistente`);
+      } else {
+        console.log(`✓ Delete inexistente retornou error`);
+      }
+    } else {
+      console.log(`⊘ Pulado (DELETE não suportado)`);
+    }
+
+    console.log('\nTestando comando INVÁLIDO...');
+    const invalidRes = await client.sendCommand('invalid comando\r');
+    if (invalidRes !== 'error') {
+      console.log(`⚠ Comando inválido deveria retornar error`);
+    } else {
+      console.log(`✓ Comando inválido retornou error`);
+    }
+
+    client.disconnect();
+    
+    console.log('\n✓✓✓ TESTES BÁSICOS CONCLUÍDOS ✓✓✓');
+    console.log('\nFuncionalidades disponíveis:');
+    console.log(`  WRITE: ${features.write ? '✓' : '✗'}`);
+    console.log(`  READ: ${features.read ? '✓' : '✗'}`);
+    console.log(`  DELETE: ${features.delete ? '✓' : '✗'}`);
+    console.log(`  KEYS: ${features.keys ? '✓' : '✗'}`);
+    console.log(`  READS: ${features.reads ? '✓' : '✗'}`);
+    console.log(`  STATUS: ${features.status ? '✓' : '✗'}`);
+    
+    return features;
+  } catch (err) {
+    client.disconnect();
+    console.error(`✗ ERRO FATAL: ${err.message}`);
+    return null;
+  }
+}
+
+async function testMillionRecords() {
+  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
+  console.log('  TESTE 2: 1 MILHÃO DE REGISTROS');
+  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
+  const client = new KVDBClient();
+  const totalRecords = 1000000;
+
+  try {
+    await client.connect();
+    console.log(`✓ Conexão persistente estabelecida\n`);
+    
+    console.log(`Iniciando escrita de ${totalRecords.toLocaleString()} registros...\n`);
+    const writeStart = Date.now();
+    let successCount = 0;
+    let errorCount = 0;
+
+    for (let i = 0; i < totalRecords; i++) {
+      try {
+        const key = generateKey('load', i);
+        const value = `value_${i}`;
+        await client.write(key, value);
+        successCount++;
+
+        if ((i + 1) % 10000 === 0) {
+          const elapsed = (Date.now() - writeStart) / 1000;
+          const rate = Math.floor((i + 1) / elapsed);
+          const progress = ((i + 1) / totalRecords * 100).toFixed(1);
+          process.stdout.write(`\r  ${(i + 1).toLocaleString()} escritas | ${rate.toLocaleString()} ops/s | ${progress}% | erros: ${errorCount}     `);
+        }
+      } catch (err) {
+        errorCount++;
+        if (errorCount < 5) {
+          console.error(`\n  Erro no registro ${i}: ${err.message}`);
+        }
+        if (!client.connected) {
+          await client.connect();
+        }
+      }
+    }
+
+    const writeTime = (Date.now() - writeStart) / 1000;
+    const writeRate = Math.floor(successCount / writeTime);
+    console.log(`\n\n✓ Escrita concluída em ${writeTime.toFixed(2)}s | ${writeRate.toLocaleString()} ops/s`);
+    console.log(`  Sucessos: ${successCount.toLocaleString()} | Erros: ${errorCount}`);
+
+    console.log('\nIniciando leitura e validação de todos os registros...\n');
+    const readStart = Date.now();
+    let readSuccess = 0;
+    let readErrors = 0;
+    let validationErrors = 0;
+
+    for (let i = 0; i < totalRecords; i++) {
+      try {
+        const key = generateKey('load', i);
+        const expectedValue = `value_${i}`;
+        const actualValue = await client.read(key);
+        
+        if (actualValue !== expectedValue) {
+          validationErrors++;
+          if (validationErrors <= 5) {
+            console.error(`\n  Validação falhou para ${key}: esperado "${expectedValue}", recebido "${actualValue}"`);
+          }
+        }
+        
+        readSuccess++;
+
+        if ((i + 1) % 10000 === 0) {
+          const elapsed = (Date.now() - readStart) / 1000;
+          const rate = Math.floor((i + 1) / elapsed);
+          const progress = ((i + 1) / totalRecords * 100).toFixed(1);
+          process.stdout.write(`\r  ${(i + 1).toLocaleString()} leituras | ${rate.toLocaleString()} reads/s | ${progress}% | erros: ${readErrors} | validação: ${validationErrors}     `);
+        }
+      } catch (err) {
+        readErrors++;
+        if (readErrors <= 5) {
+          console.error(`\n  Erro na leitura ${i}: ${err.message}`);
+        }
+        if (!client.connected) {
+          await client.connect();
+        }
+      }
+    }
+
+    const readTime = (Date.now() - readStart) / 1000;
+    const readRate = Math.floor(readSuccess / readTime);
+    console.log(`\n\n✓ Leitura e validação concluída em ${readTime.toFixed(2)}s | ${readRate.toLocaleString()} reads/s`);
+    console.log(`  Sucessos: ${readSuccess.toLocaleString()} | Erros: ${readErrors} | Erros de validação: ${validationErrors}`);
+
+    client.disconnect();
+    console.log('\n✓ Teste de 1 milhão concluído com sucesso');
+    return true;
+  } catch (err) {
+    client.disconnect();
+    console.error(`✗ ERRO: ${err.message}`);
+    return false;
+  }
+}
+
+async function testMultipleReads(features) {
+  if (!features.reads) {
+    console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
+    console.log('  TESTE 3: LEITURAS MÚLTIPLAS (READS) - PULADO');
+    console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
+    console.log('⊘ Comando READS não suportado pelo servidor');
+    console.log('  Este teste compararia o desempenho de:');
+    console.log('  - READS prefix (bulk read)');
+    console.log('  - vs múltiplos READ individuais');
+    return false;
+  }
+
+  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
+  console.log('  TESTE 3: LEITURAS MÚLTIPLAS (READS vs READ)');
+  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
+  
+  const client = new KVDBClient();
+  const prefixes = ['user', 'product', 'order', 'session', 'cache'];
+  const recordsPerPrefix = 1000;
+
+  try {
+    await client.connect();
+    console.log('✓ Conexão estabelecida\n');
+
+    // Preparar dados de teste
+    console.log(`Preparando ${prefixes.length * recordsPerPrefix} registros...`);
+    for (const prefix of prefixes) {
+      for (let i = 0; i < recordsPerPrefix; i++) {
+        const key = `${prefix}:${i}`;
+        const value = `${prefix}_value_${i}`;
+        await client.write(key, value);
+      }
+      console.log(`  ✓ ${recordsPerPrefix} registros com prefixo "${prefix}"`);
+    }
+
+    console.log('\n' + '─'.repeat(60));
+    
+    // Teste 1: Usando READS (bulk)
+    console.log('\nTeste 1: Usando READS (bulk read por prefixo)');
+    const bulkStart = Date.now();
+    const bulkResults = {};
+    
+    for (const prefix of prefixes) {
+      const result = await client.reads(prefix);
+      const values = result.split('\n').filter(v => v.trim());
+      bulkResults[prefix] = values.length;
+      console.log(`  ${prefix}: ${values.length} valores em ${Date.now() - bulkStart}ms`);
+    }
+    
+    const bulkTime = Date.now() - bulkStart;
+    const bulkTotal = Object.values(bulkResults).reduce((a, b) => a + b, 0);
+    console.log(`\n✓ READS completado: ${bulkTotal} valores em ${bulkTime}ms`);
+    console.log(`  Throughput: ${Math.floor((bulkTotal / bulkTime) * 1000)} valores/s`);
+
+    // Teste 2: Usando READ individual
+    console.log('\nTeste 2: Usando READ individual (um por um)');
+    const individualStart = Date.now();
+    let individualCount = 0;
+    
+    for (const prefix of prefixes) {
+      const prefixStart = Date.now();
+      for (let i = 0; i < recordsPerPrefix; i++) {
+        const key = `${prefix}:${i}`;
+        await client.read(key);
+        individualCount++;
+      }
+      console.log(`  ${prefix}: ${recordsPerPrefix} reads em ${Date.now() - prefixStart}ms`);
+    }
+    
+    const individualTime = Date.now() - individualStart;
+    console.log(`\n✓ READ individual completado: ${individualCount} leituras em ${individualTime}ms`);
+    console.log(`  Throughput: ${Math.floor((individualCount / individualTime) * 1000)} reads/s`);
+
+    // Comparação
+    console.log('\n' + '─'.repeat(60));
+    console.log('COMPARAÇÃO DE DESEMPENHO:');
+    console.log(`  READS (bulk):     ${bulkTime}ms`);
+    console.log(`  READ (individual): ${individualTime}ms`);
+    console.log(`  Diferença:         ${individualTime - bulkTime}ms`);
+    console.log(`  READS é ${(individualTime / bulkTime).toFixed(2)}x mais rápido`);
+    console.log('─'.repeat(60));
+
+    client.disconnect();
+    console.log('\n✓ Teste de leituras múltiplas concluído');
+    return true;
+  } catch (err) {
+    client.disconnect();
+    console.error(`✗ ERRO: ${err.message}`);
+    return false;
+  }
+}
+
+async function testConcurrency() {
+  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
+  console.log('  TESTE 4: CONCORRÊNCIA (10 CONEXÕES x 100K = 1M REGISTROS)');
+  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
+  
+  const numConnections = 10;
+  const recordsPerConnection = 100000;
+  const totalRecords = numConnections * recordsPerConnection;
+
+  try {
+    console.log(`Iniciando ${numConnections} conexões paralelas...\n`);
+    const startTime = Date.now();
+    const connectionStats = [];
+
+    const promises = [];
+    for (let conn = 0; conn < numConnections; conn++) {
+      const promise = (async (connectionId) => {
+        const client = new KVDBClient();
+        await client.connect();
+        
+        const connStart = Date.now();
+        let success = 0;
+        let errors = 0;
+
+        for (let i = 0; i < recordsPerConnection; i++) {
+          try {
+            const key = generateKey(`conn${connectionId}`, i);
+            const value = `conn${connectionId}_value_${i}`;
+            await client.write(key, value);
+            success++;
+
+            if ((i + 1) % 10000 === 0) {
+              const elapsed = (Date.now() - connStart) / 1000;
+              const rate = Math.floor((i + 1) / elapsed);
+              const progress = ((i + 1) / recordsPerConnection * 100).toFixed(0);
+              console.log(`  [Conn ${connectionId}] ${(i + 1).toLocaleString()} registros | ${rate.toLocaleString()} ops/s | ${progress}%`);
+            }
+          } catch (err) {
+            errors++;
+            if (!client.connected) {
+              await client.connect();
+            }
+          }
+        }
+
+        const connTime = (Date.now() - connStart) / 1000;
+        const connRate = Math.floor(success / connTime);
+        connectionStats.push({ id: connectionId, time: connTime, rate: connRate, errors });
+        
+        console.log(`✓ Conexão ${connectionId} finalizada: ${connTime.toFixed(2)}s | ${connRate.toLocaleString()} ops/s | erros: ${errors}`);
+        
+        client.disconnect();
+      })(conn);
+
+      promises.push(promise);
+    }
+
+    await Promise.all(promises);
+
+    const totalTime = (Date.now() - startTime) / 1000;
+    const totalRate = Math.floor(totalRecords / totalTime);
+
+    console.log('\n' + '─'.repeat(60));
+    console.log(`RESULTADOS DE ESCRITA:`);
+    console.log(`  Total de registros: ${totalRecords.toLocaleString()}`);
+    console.log(`  Tempo total: ${totalTime.toFixed(2)}s`);
+    console.log(`  Throughput agregado: ${totalRate.toLocaleString()} ops/s`);
+    console.log('─'.repeat(60));
+
+    // Ranking
+    console.log('\nRanking de Performance (escrita):');
+    connectionStats.sort((a, b) => b.rate - a.rate);
+    connectionStats.forEach((stat, idx) => {
+      const position = `${idx + 1}º`.padEnd(4);
+      console.log(`  ${position} Conexão ${stat.id}: ${stat.rate.toLocaleString().padStart(8)} ops/s (${stat.time.toFixed(2)}s)`);
+    });
+
+    console.log('\nIniciando leituras e validação paralelas...\n');
+    const readStart = Date.now();
+    const readStats = [];
+
+    const readPromises = [];
+    for (let conn = 0; conn < numConnections; conn++) {
+      const promise = (async (connectionId) => {
+        const client = new KVDBClient();
+        await client.connect();
+        
+        let success = 0;
+        let errors = 0;
+        let validationErrors = 0;
+        const readConnStart = Date.now();
+
+        for (let i = 0; i < recordsPerConnection; i++) {
+          try {
+            const key = generateKey(`conn${connectionId}`, i);
+            const expectedValue = `conn${connectionId}_value_${i}`;
+            const actualValue = await client.read(key);
+            
+            if (actualValue !== expectedValue) {
+              validationErrors++;
+              if (validationErrors <= 5) {
+                console.error(`\n  [Conn ${connectionId}] Validação falhou para ${key}: esperado "${expectedValue}", recebido "${actualValue}"`);
+              }
+            }
+            
+            success++;
+            
+            if ((i + 1) % 10000 === 0) {
+              const elapsed = (Date.now() - readConnStart) / 1000;
+              const rate = Math.floor((i + 1) / elapsed);
+              const progress = ((i + 1) / recordsPerConnection * 100).toFixed(0);
+              console.log(`  [Conn ${connectionId}] ${(i + 1).toLocaleString()} leituras | ${rate.toLocaleString()} reads/s | ${progress}% | validação: ${validationErrors}`);
+            }
+          } catch (err) {
+            errors++;
+            if (!client.connected) {
+              await client.connect();
+            }
+          }
+        }
+
+        const readConnTime = (Date.now() - readConnStart) / 1000;
+        const readConnRate = Math.floor(success / readConnTime);
+        readStats.push({ id: connectionId, time: readConnTime, rate: readConnRate, errors, validationErrors });
+
+        console.log(`✓ Conexão ${connectionId}: ${success.toLocaleString()} leituras | ${readConnRate.toLocaleString()} reads/s | validação: ${validationErrors}`);
+        
+        client.disconnect();
+      })(conn);
+
+      readPromises.push(promise);
+    }
+
+    await Promise.all(readPromises);
+
+    const readTime = (Date.now() - readStart) / 1000;
+    const totalReads = numConnections * recordsPerConnection;
+    const readTotalRate = Math.floor(totalReads / readTime);
+    const totalValidationErrors = readStats.reduce((sum, stat) => sum + stat.validationErrors, 0);
+
+    console.log('\n' + '─'.repeat(60));
+    console.log(`RESULTADOS DE LEITURA E VALIDAÇÃO:`);
+    console.log(`  Total de leituras: ${totalReads.toLocaleString()}`);
+    console.log(`  Tempo total: ${readTime.toFixed(2)}s`);
+    console.log(`  Throughput agregado: ${readTotalRate.toLocaleString()} reads/s`);
+    console.log(`  Erros de validação: ${totalValidationErrors}`);
+    console.log('─'.repeat(60));
+
+    // Ranking de leituras
+    console.log('\nRanking de Performance (leitura):');
+    readStats.sort((a, b) => b.rate - a.rate);
+    readStats.forEach((stat, idx) => {
+      const position = `${idx + 1}º`.padEnd(4);
+      console.log(`  ${position} Conexão ${stat.id}: ${stat.rate.toLocaleString().padStart(8)} reads/s | validação: ${stat.validationErrors}`);
+    });
+
+    console.log('\n✓ Teste de concorrência concluído com sucesso');
+    return true;
+  } catch (err) {
+    console.error(`✗ ERRO: ${err.message}`);
+    return false;
+  }
+}
+
+async function runAllTests() {
+  console.log('\n╔════════════════════════════════════════════════════╗');
+  console.log('║       PIZZAKV - SUITE DE TESTES DE PERFORMANCE    ║');
+  console.log('║                   Porta: 8080                      ║');
+  console.log('╚════════════════════════════════════════════════════╝');
+
+  const features = await testBasicFeatures();
+  
+  if (!features) {
+    console.log('\n✗ Erro fatal nos testes básicos. Abortando...');
+    process.exit(1);
+  }
+  
+  if (!features.write || !features.read) {
+    console.log('\n✗ WRITE e READ são obrigatórios. Abortando...');
+    process.exit(1);
+  }
+  
+  console.log('\n\nIniciando teste de 1 milhão em 3 segundos...');
+  await new Promise(resolve => setTimeout(resolve, 3000));
+  
+  const success2 = await testMillionRecords();
+  
+  if (!success2) {
+    console.log('\n⚠ Teste de 1M falhou, mas continuando...');
+  }
+  
+  console.log('\n\nIniciando teste de leituras múltiplas em 3 segundos...');
+  await new Promise(resolve => setTimeout(resolve, 3000));
+  
+  const success3 = await testMultipleReads(features);
+  
+  if (!success3 && features.reads) {
+    console.log('\n⚠ Teste de leituras múltiplas falhou');
+  }
+  
+  console.log('\n\nIniciando teste de concorrência em 3 segundos...');
+  await new Promise(resolve => setTimeout(resolve, 3000));
+  
+  const success4 = await testConcurrency();
+
+  if (!success4) {
+    console.log('\n⚠ Teste de concorrência falhou');
+  }
+
+  console.log('\n╔════════════════════════════════════════════════════╗');
+  console.log('║            ✓ TODOS OS TESTES CONCLUÍDOS           ║');
+  console.log('╚════════════════════════════════════════════════════╝\n');
+}
+
+runAllTests().catch(console.error);