Что такое RocksDbSharp?
RocksDbSharp — это официальная .NET обёртка для RocksDB от Facebook, поддерживающая:
- .NET Standard 2.0+ (совместимость с PowerShell 7.5)
- Все функции RocksDB C API
- Управление памятью через safe handles
- Более 100% покрытия API
Установка и настройка
1. Установка через NuGet
# PowerShell 7.5 с поддержкой PackageManagement
Install-PackageProvider -Name NuGet -Force -MinimumVersion 2.8.5.201
# Установка RocksDbSharp
Install-Package RocksDbSharp -ProviderName NuGet
# Или через dotnet CLI (альтернативный способ)
dotnet add package RocksDbSharp --version 8.3.2
2. Автоматическая установка с зависимостями
function Install-RocksDbSharp {
param(
[string]$InstallPath = ".\lib"
)
# Создаём каталог для библиотек
New-Item -ItemType Directory -Path $InstallPath -Force
# Скачиваем NuGet пакет
$packageUrl = "https://www.nuget.org/api/v2/package/RocksDbSharp/8.3.2"
$tempFile = Join-Path $env:TEMP "rocksdbsharp.nupkg"
Write-Host "Скачивание RocksDbSharp..." -ForegroundColor Yellow
Invoke-WebRequest -Uri $packageUrl -OutFile $tempFile
# Распаковываем
Write-Host "Распаковка..." -ForegroundColor Yellow
Expand-Archive -Path $tempFile -DestinationPath "$env:TEMP\rocksdbsharp" -Force
# Копируем нужные сборки
$sourceDir = "$env:TEMP\rocksdbsharp\lib\netstandard2.0"
Copy-Item "$sourceDir\*.dll" $InstallPath -Force
# Проверяем наличие rocksdb.dll (native dependency)
if (-not (Test-Path "$InstallPath\rocksdb.dll")) {
Write-Warning "rocksdb.dll не найдена. Скачиваем..."
$rocksdbUrl = "https://github.com/facebook/rocksdb/releases/download/v8.3.2/rocksdb-8.3.2-win64.zip"
$rocksdbZip = Join-Path $env:TEMP "rocksdb.zip"
Invoke-WebRequest -Uri $rocksdbUrl -OutFile $rocksdbZip
Expand-Archive -Path $rocksdbZip -DestinationPath "$env:TEMP\rocksdb" -Force
Copy-Item "$env:TEMP\rocksdb\rocksdb.dll" $InstallPath -Force
}
# Загружаем сборки
Add-Type -Path "$InstallPath\RocksDbSharp.dll"
Write-Host "RocksDbSharp установлен в $InstallPath" -ForegroundColor Green
return $InstallPath
}
# Установка
$libPath = Install-RocksDbSharp -InstallPath "C:\PowerShell\Lib"
Базовые операции с RocksDbSharp
1. Открытие/создание базы данных
using namespace RocksDbSharp
# Настройка опций базы данных
$options = [RocksDbSharp.DbOptions]::new()
# Основные опции
$options.SetCreateIfMissing($true) # Создать если не существует
$options.SetIncreaseParallelism([Environment]::ProcessorCount) # Использовать все ядра
$options.OptimizeLevelStyleCompaction(64 * 1024 * 1024) # 64MB мемтаблица
# Опции записи
$writeOptions = [RocksDbSharp.WriteOptions]::new()
$writeOptions.SetSync($false) # Асинхронная запись
# Опции чтения
$readOptions = [RocksDbSharp.ReadOptions]::new()
$readOptions.SetVerifyChecksums($false) # Для производительности
# Открытие базы
$dbPath = "C:\Data\RocksDB\TestDB"
$db = [RocksDbSharp.RocksDb]::Open($options, $dbPath)
Write-Host "База открыта: $dbPath" -ForegroundColor Green
2. Полная обёртка для PowerShell
class PowerRocksDb {
[RocksDbSharp.RocksDb]$Db
[RocksDbSharp.DbOptions]$Options
[RocksDbSharp.WriteOptions]$WriteOptions
[RocksDbSharp.ReadOptions]$ReadOptions
[RocksDbSharp.ColumnFamilyOptions]$CfOptions
[string]$DbPath
[hashtable]$ColumnFamilies = @{}
# Конструктор
PowerRocksDb([string]$path) {
$this.DbPath = $path
$this.Initialize()
}
# Инициализация с настройками для PowerShell
[void]Initialize() {
# Основные опции
$this.Options = [RocksDbSharp.DbOptions]::new()
$this.Options.SetCreateIfMissing($true)
$this.Options.SetIncreaseParallelism([Environment]::ProcessorCount)
$this.Options.OptimizeLevelStyleCompaction(64 * 1024 * 1024)
$this.Options.SetMaxOpenFiles(-1) # Неограниченное кол-во файлов
$this.Options.SetUseDirectReads($true) # Прямое чтение для SSD
$this.Options.SetUseDirectIoForFlushAndCompaction($true)
$this.Options.SetTargetFileSizeBase(64 * 1024 * 1024) # 64MB
$this.Options.SetWriteBufferSize(64 * 1024 * 1024) # 64MB мемтаблица
$this.Options.SetMaxWriteBufferNumber(3) # Количество мемтаблиц
$this.Options.SetMinWriteBufferNumberToMerge(2) # Минимальное слияние
# Включение сжатия (опционально)
$this.Options.SetCompression([RocksDbSharp.Compression]::SnappyCompression)
$this.Options.SetBottommostCompression([RocksDbSharp.Compression]::ZSTDCompression)
# Опции для Column Families
$this.CfOptions = [RocksDbSharp.ColumnFamilyOptions]::new()
$this.CfOptions.OptimizeLevelStyleCompaction(64 * 1024 * 1024)
# Опции записи
$this.WriteOptions = [RocksDbSharp.WriteOptions]::new()
$this.WriteOptions.SetSync($false) # Асинхронная запись
$this.WriteOptions.DisableWal($false) # Включить WAL
# Опции чтения
$this.ReadOptions = [RocksDbSharp.ReadOptions]::new()
$this.ReadOptions.SetVerifyChecksums($false) # Для производительности
$this.ReadOptions.SetFillCache($true) # Заполнять кэш
}
# Открытие базы данных
[void]Open() {
$this.EnsureDirectory()
# Пытаемся открыть существующие Column Families
$existingCFs = [RocksDbSharp.RocksDb]::ListColumnFamilies($this.Options, $this.DbPath)
if ($existingCFs -and $existingCFs.Count -gt 0) {
# Открываем с существующими CF
$cfHandles = @()
$cfNames = @()
foreach ($cfName in $existingCFs) {
$cfNames += $cfName
}
$handles = @{}
$this.Db = [RocksDbSharp.RocksDb]::Open($this.Options, $this.DbPath, $cfNames, $cfHandles)
# Сохраняем handles для Column Families
for ($i = 0; $i -lt $cfNames.Count; $i++) {
$this.ColumnFamilies[$cfNames[$i]] = $cfHandles[$i]
}
} else {
# Создаём новую базу с default CF
$this.Db = [RocksDbSharp.RocksDb]::Open($this.Options, $this.DbPath)
$this.ColumnFamilies["default"] = $null # Default handle
}
Write-Host "База открыта: $($this.DbPath)" -ForegroundColor Green
}
# Создание Column Family
[RocksDbSharp.ColumnFamilyHandle]CreateColumnFamily([string]$name) {
if ($this.ColumnFamilies.ContainsKey($name)) {
return $this.ColumnFamilies[$name]
}
$handle = $this.Db.CreateColumnFamily($this.CfOptions, $name)
$this.ColumnFamilies[$name] = $handle
Write-Host "Создана Column Family: $name" -ForegroundColor Yellow
return $handle
}
# Запись ключ-значение
[void]Put([string]$key, [object]$value, [string]$columnFamily = "default") {
$cfHandle = $this.GetColumnFamilyHandle($columnFamily)
if ($value -is [string]) {
$valueBytes = [System.Text.Encoding]::UTF8.GetBytes($value)
} elseif ($value -is [byte[]]) {
$valueBytes = $value
} else {
$valueBytes = [System.Text.Encoding]::UTF8.GetBytes(($value | ConvertTo-Json -Compress))
}
$keyBytes = [System.Text.Encoding]::UTF8.GetBytes($key)
if ($cfHandle) {
$this.Db.Put($keyBytes, $valueBytes, $cfHandle, $this.WriteOptions)
} else {
$this.Db.Put($keyBytes, $valueBytes, $this.WriteOptions)
}
}
# Пакетная запись
[void]BatchPut([hashtable]$keyValues, [string]$columnFamily = "default") {
$cfHandle = $this.GetColumnFamilyHandle($columnFamily)
$batch = [RocksDbSharp.WriteBatch]::new()
foreach ($kv in $keyValues.GetEnumerator()) {
$keyBytes = [System.Text.Encoding]::UTF8.GetBytes($kv.Key)
if ($kv.Value -is [string]) {
$valueBytes = [System.Text.Encoding]::UTF8.GetBytes($kv.Value)
} elseif ($kv.Value -is [byte[]]) {
$valueBytes = $kv.Value
} else {
$valueBytes = [System.Text.Encoding]::UTF8.GetBytes(($kv.Value | ConvertTo-Json -Compress))
}
$batch.Put($keyBytes, $valueBytes, $cfHandle)
}
if ($cfHandle) {
$this.Db.Write($batch, $this.WriteOptions, $cfHandle)
} else {
$this.Db.Write($batch, $this.WriteOptions)
}
$batch.Dispose()
}
# Чтение по ключу
[object]Get([string]$key, [string]$columnFamily = "default") {
$cfHandle = $this.GetColumnFamilyHandle($columnFamily)
$keyBytes = [System.Text.Encoding]::UTF8.GetBytes($key)
$valueBytes = if ($cfHandle) {
$this.Db.Get($keyBytes, $cfHandle, $this.ReadOptions)
} else {
$this.Db.Get($keyBytes, $this.ReadOptions)
}
if ($valueBytes -eq $null) { return $null }
# Пытаемся декодировать как строку UTF8
try {
$stringValue = [System.Text.Encoding]::UTF8.GetString($valueBytes)
# Пытаемся разобрать как JSON
if ($stringValue.StartsWith("{") -or $stringValue.StartsWith("[")) {
try {
return $stringValue | ConvertFrom-Json
} catch {
return $stringValue
}
}
return $stringValue
} catch {
# Возвращаем как byte[] если не UTF8
return $valueBytes
}
}
# Удаление ключа
[void]Delete([string]$key, [string]$columnFamily = "default") {
$cfHandle = $this.GetColumnFamilyHandle($columnFamily)
$keyBytes = [System.Text.Encoding]::UTF8.GetBytes($key)
if ($cfHandle) {
$this.Db.Remove($keyBytes, $cfHandle, $this.WriteOptions)
} else {
$this.Db.Remove($keyBytes, $this.WriteOptions)
}
}
# Поиск по префиксу
[System.Collections.Generic.List[PSObject]]GetByPrefix([string]$prefix, [string]$columnFamily = "default") {
$results = [System.Collections.Generic.List[PSObject]]::new()
$cfHandle = $this.GetColumnFamilyHandle($columnFamily)
$prefixBytes = [System.Text.Encoding]::UTF8.GetBytes($prefix)
$iterator = if ($cfHandle) {
$this.Db.NewIterator($cfHandle, $this.ReadOptions)
} else {
$this.Db.NewIterator($this.ReadOptions)
}
try {
$iterator.Seek($prefixBytes)
while ($iterator.Valid()) {
$key = [System.Text.Encoding]::UTF8.GetString($iterator.Key())
# Проверяем префикс
if (-not $key.StartsWith($prefix)) {
break
}
$valueBytes = $iterator.Value()
$value = if ($valueBytes) {
try {
[System.Text.Encoding]::UTF8.GetString($valueBytes)
} catch {
$valueBytes
}
} else {
$null
}
$results.Add([PSCustomObject]@{
Key = $key
Value = $value
ColumnFamily = $columnFamily
})
$iterator.Next()
}
}
finally {
$iterator.Dispose()
}
return $results
}
# Получение всех ключей
[System.Collections.Generic.List[string]]GetAllKeys([string]$columnFamily = "default") {
$keys = [System.Collections.Generic.List[string]]::new()
$cfHandle = $this.GetColumnFamilyHandle($columnFamily)
$iterator = if ($cfHandle) {
$this.Db.NewIterator($cfHandle, $this.ReadOptions)
} else {
$this.Db.NewIterator($this.ReadOptions)
}
try {
$iterator.SeekToFirst()
while ($iterator.Valid()) {
$key = [System.Text.Encoding]::UTF8.GetString($iterator.Key())
$keys.Add($key)
$iterator.Next()
}
}
finally {
$iterator.Dispose()
}
return $keys
}
# Приблизительное количество записей
[long]GetApproximateCount([string]$columnFamily = "default") {
$cfHandle = $this.GetColumnFamilyHandle($columnFamily)
if ($cfHandle) {
return $this.Db.GetLongProperty("rocksdb.estimate-num-keys", $cfHandle)
} else {
return $this.Db.GetLongProperty("rocksdb.estimate-num-keys")
}
}
# Компактизация
[void]Compact() {
$this.Db.CompactRange($null, $null)
Write-Host "Компактизация завершена" -ForegroundColor Yellow
}
# Создание снапшота
[RocksDbSharp.Snapshot]CreateSnapshot() {
return $this.Db.CreateSnapshot()
}
# Получение статистики
[hashtable]GetStats() {
$stats = @{
Path = $this.DbPath
ColumnFamilies = $this.ColumnFamilies.Keys -join ", "
ApproximateKeys = $this.GetApproximateCount()
Properties = @{}
}
# Собираем свойства
$properties = @(
"rocksdb.num-files-at-level0",
"rocksdb.num-files-at-level1",
"rocksdb.num-files-at-level2",
"rocksdb.estimate-table-readers-mem",
"rocksdb.cur-size-all-mem-tables",
"rocksdb.block-cache-usage",
"rocksdb.block-cache-capacity"
)
foreach ($prop in $properties) {
try {
$value = $this.Db.GetProperty($prop)
$stats.Properties[$prop] = $value
} catch {
# Игнорируем недоступные свойства
}
}
return $stats
}
# Закрытие базы
[void]Close() {
# Закрываем все Column Family handles
foreach ($handle in $this.ColumnFamilies.Values) {
if ($handle -ne $null) {
$handle.Dispose()
}
}
$this.ColumnFamilies.Clear()
if ($this.Db -ne $null) {
$this.Db.Dispose()
$this.Db = $null
}
Write-Host "База закрыта" -ForegroundColor Yellow
}
# Полное удаление базы
[void]Destroy() {
$this.Close()
[RocksDbSharp.RocksDb]::DestroyDb($this.DbPath, $this.Options)
Write-Host "База удалена: $($this.DbPath)" -ForegroundColor Red
}
# Приватные вспомогательные методы
hidden [RocksDbSharp.ColumnFamilyHandle]GetColumnFamilyHandle([string]$name) {
if ($name -eq "default" -or -not $this.ColumnFamilies.ContainsKey($name)) {
return $null
}
return $this.ColumnFamilies[$name]
}
hidden [void]EnsureDirectory() {
$dir = [System.IO.Path]::GetDirectoryName($this.DbPath)
if (-not [string]::IsNullOrEmpty($dir) -and -not (Test-Path $dir)) {
New-Item -ItemType Directory -Path $dir -Force | Out-Null
}
}
# Деструктор
Dispose() {
$this.Close()
[GC]::SuppressFinalize($this)
}
}
3. Примеры использования
# Пример 1: Базовые операции
try {
$db = [PowerRocksDb]::new("C:\Data\RocksDB\MyApp")
$db.Open()
# Запись данных
$db.Put("user:1001", @{
Name = "Иван Иванов"
Email = "ivan@example.com"
Age = 30
Created = (Get-Date).ToString("o")
})
$db.Put("config:site_name", "Мой сайт")
$db.Put("config:version", "1.0.0")
# Пакетная запись
$batchData = @{}
1..1000 | ForEach-Object {
$batchData["log:$(Get-Date -Format 'yyyy-MM-dd'):$_"] = "Сообщение лога номер $_"
}
$db.BatchPut($batchData)
# Чтение данных
$user = $db.Get("user:1001")
Write-Host "Пользователь: $($user.Name)" -ForegroundColor Green
# Поиск по префиксу
$configs = $db.GetByPrefix("config:")
$configs | ForEach-Object {
Write-Host "$($_.Key): $($_.Value)"
}
# Статистика
$stats = $db.GetStats()
$stats.Properties.GetEnumerator() | Format-Table
} finally {
if ($db -ne $null) {
$db.Dispose()
}
}
# Пример 2: Column Families (подобно таблицам)
try {
$db = [PowerRocksDb]::new("C:\Data\RocksDB\MultiTable")
$db.Open()
# Создаём Column Families для разных типов данных
$usersCF = $db.CreateColumnFamily("users")
$logsCF = $db.CreateColumnFamily("logs")
$configsCF = $db.CreateColumnFamily("configs")
# Запись в разные Column Families
$db.Put("ivanov", @{Name="Иван"; Age=25}, "users")
$db.Put("petrov", @{Name="Пётр"; Age=30}, "users")
$db.Put("site_title", "Мой сайт", "configs")
$db.Put("error:2024-01-15", "Ошибка БД", "logs")
# Чтение из конкретной Column Family
$user = $db.Get("ivanov", "users")
Write-Host "Пользователь: $($user.Name)" -ForegroundColor Green
# Поиск по префиксу в Column Family
$allUsers = $db.GetByPrefix("", "users")
Write-Host "Всего пользователей: $($allUsers.Count)"
# Количество записей в каждой CF
Write-Host "Пользователей: $($db.GetApproximateCount('users'))"
Write-Host "Логов: $($db.GetApproximateCount('logs'))"
} finally {
if ($db -ne $null) {
$db.Dispose()
}
}
# Пример 3: Работа с бинарными данными
try {
$db = [PowerRocksDb]::new("C:\Data\RocksDB\BinaryStorage")
$db.Open()
# Сохраняем файл в RocksDB
$filePath = "C:\Windows\System32\notepad.exe"
$fileBytes = [System.IO.File]::ReadAllBytes($filePath)
$db.Put("file:notepad.exe", $fileBytes)
# Сохраняем сжатые данные
$largeText = "A" * 1000000 # 1MB текста
$compressed = [System.IO.Compression.Compression]::Compress(
[System.Text.Encoding]::UTF8.GetBytes($largeText)
)
$db.Put("compressed:text", $compressed)
# Восстановление файла
$savedBytes = $db.Get("file:notepad.exe")
[System.IO.File]::WriteAllBytes("C:\Temp\notepad_restored.exe", $savedBytes)
} finally {
if ($db -ne $null) {
$db.Dispose()
}
}
4. Продвинутые возможности
# Пример 4: Транзакции и снапшоты
try {
$db = [PowerRocksDb]::new("C:\Data\RocksDB\Transactions")
$db.Open()
# Создаём снапшот для point-in-time восстановления
$snapshot = $db.CreateSnapshot()
# Используем WriteBatch для атомарных операций
$batch = [RocksDbSharp.WriteBatch]::new()
# Несколько операций в одной транзакции
$batch.Put([System.Text.Encoding]::UTF8.GetBytes("account:1001:balance"),
[System.Text.Encoding]::UTF8.GetBytes("1000"))
$batch.Put([System.Text.Encoding]::UTF8.GetBytes("account:1002:balance"),
[System.Text.Encoding]::UTF8.GetBytes("500"))
$batch.Delete([System.Text.Encoding]::UTF8.GetBytes("temp:data"))
# Выполняем батч
$writeOptions = [RocksDbSharp.WriteOptions]::new()
$writeOptions.SetSync($true) # Синхронная запись для транзакций
$db.Db.Write($batch, $writeOptions)
# Откат через WriteBatch можно сымитировать
$compensatingBatch = [RocksDbSharp.WriteBatch]::new()
$compensatingBatch.Put([System.Text.Encoding]::UTF8.GetBytes("account:1001:balance"),
[System.Text.Encoding]::UTF8.GetBytes("950"))
$compensatingBatch.Put([System.Text.Encoding]::UTF8.GetBytes("account:1002:balance"),
[System.Text.Encoding]::UTF8.GetBytes("550"))
# Применяем компенсирующие операции
$db.Db.Write($compensatingBatch, $writeOptions)
# Проверяем результат
$balance1 = $db.Get("account:1001:balance")
Write-Host "Баланс счёта 1001: $balance1"
} finally {
if ($db -ne $null) {
$db.Dispose()
}
}
# Пример 5: Оптимизация для временных рядов
class TimeSeriesRocksDb : PowerRocksDb {
TimeSeriesRocksDb([string]$path) : base($path) {
# Оптимизация для временных рядов
$this.Options.SetCompactionStyle([RocksDbSharp.Compaction]::Level)
$this.Options.OptimizeUniversalStyleCompaction()
$this.Options.SetTargetFileSizeMultiplier(10)
$this.Options.SetMaxBytesForLevelBase(256 * 1024 * 1024) # 256MB
}
[void]AddTimeSeriesPoint([string]$series, [DateTime]$timestamp, [object]$value) {
# Ключ: series:timestamp_ticks
$key = "$series:$($timestamp.Ticks)"
$this.Put($key, @{
Timestamp = $timestamp.ToString("o")
Value = $value
Series = $series
})
}
[System.Collections.Generic.List[PSObject]]GetTimeSeriesRange(
[string]$series,
[DateTime]$start,
[DateTime]$end
) {
$prefix = "$series:"
$startKey = "$prefix$($start.Ticks)"
$endKey = "$prefix$($end.Ticks)"
$results = [System.Collections.Generic.List[PSObject]]::new()
$iterator = $this.Db.NewIterator($this.ReadOptions)
try {
$iterator.Seek([System.Text.Encoding]::UTF8.GetBytes($startKey))
while ($iterator.Valid()) {
$key = [System.Text.Encoding]::UTF8.GetString($iterator.Key())
# Проверяем границы
if ($key -ge $endKey) {
break
}
if ($key.StartsWith($prefix)) {
$valueBytes = $iterator.Value()
$value = [System.Text.Encoding]::UTF8.GetString($valueBytes) | ConvertFrom-Json
$results.Add($value)
}
$iterator.Next()
}
}
finally {
$iterator.Dispose()
}
return $results
}
}
# Использование временных рядов
$tsDb = [TimeSeriesRocksDb]::new("C:\Data\RocksDB\TimeSeries")
$tsDb.Open()
# Добавление точек
1..1000 | ForEach-Object {
$timestamp = (Get-Date).AddMinutes($_)
$value = [Math]::Sin($_ / 10.0) * 100
$tsDb.AddTimeSeriesPoint("sensor1", $timestamp, $value)
}
# Получение диапазона
$start = (Get-Date).AddHours(-1)
$end = Get-Date
$points = $tsDb.GetTimeSeriesRange("sensor1", $start, $end)
Write-Host "Получено точек: $($points.Count)"
$tsDb.Dispose()
5. Бенчмарк и мониторинг
function Test-RocksDbPerformance {
param(
[int]$Records = 100000,
[string]$DbPath = "C:\Data\RocksDB\Benchmark"
)
# Очистка предыдущего теста
if (Test-Path $DbPath) {
Remove-Item -Path $DbPath -Recurse -Force
}
$db = [PowerRocksDb]::new($DbPath)
$db.Open()
$results = @{}
# Тест 1: Последовательная запись
$sw = [System.Diagnostics.Stopwatch]::StartNew()
1..$Records | ForEach-Object {
$key = "key_$_"
$value = @{
Id = $_
Name = "Record $_"
Data = "x" * 100 # 100 байт данных
Timestamp = (Get-Date).ToString("o")
}
$db.Put($key, $value)
if ($_ % 10000 -eq 0) {
Write-Progress -Activity "Последовательная запись" -Status "$_ / $Records"
}
}
$sw.Stop()
$results.SequentialWrite = @{
Time = $sw.Elapsed.TotalSeconds
Throughput = [Math]::Round($Records / $sw.Elapsed.TotalSeconds, 0)
}
Write-Host "Последовательная запись: $($results.SequentialWrite.Throughput) зап/с" -ForegroundColor Green
# Тест 2: Случайное чтение
$sw.Restart()
$rnd = [Random]::new()
for ($i = 1; $i -le 10000; $i++) {
$randomKey = "key_" + $rnd.Next(1, $Records)
$null = $db.Get($randomKey)
}
$sw.Stop()
$results.RandomRead = @{
Time = $sw.Elapsed.TotalSeconds
Throughput = [Math]::Round(10000 / $sw.Elapsed.TotalSeconds, 0)
}
Write-Host "Случайное чтение: $($results.RandomRead.Throughput) зап/с" -ForegroundColor Green
# Тест 3: Поиск по префиксу
$sw.Restart()
$prefixResults = $db.GetByPrefix("key_")
$sw.Stop()
$results.PrefixScan = @{
Time = $sw.Elapsed.TotalSeconds
Count = $prefixResults.Count
Throughput = [Math]::Round($prefixResults.Count / $sw.Elapsed.TotalSeconds, 0)
}
Write-Host "Сканирование по префиксу: $($results.PrefixScan.Throughput) зап/с" -ForegroundColor Green
# Тест 4: Пакетная запись
$sw.Restart()
$batchSize = 1000
$batchCount = [Math]::Ceiling($Records / $batchSize)
for ($batch = 0; $batch -lt $batchCount; $batch++) {
$batchData = @{}
$start = $batch * $batchSize + 1
$end = [Math]::Min(($batch + 1) * $batchSize, $Records)
for ($i = $start; $i -le $end; $i++) {
$batchData["batch_$i"] = @{Id=$i; Data="Batch record $i"}
}
$db.BatchPut($batchData)
Write-Progress -Activity "Пакетная запись" -Status "Пакет $($batch+1)/$batchCount"
}
$sw.Stop()
$results.BatchWrite = @{
Time = $sw.Elapsed.TotalSeconds
Throughput = [Math]::Round($Records / $sw.Elapsed.TotalSeconds, 0)
BatchSize = $batchSize
}
Write-Host "Пакетная запись: $($results.BatchWrite.Throughput) зап/с" -ForegroundColor Green
# Статистика базы
$stats = $db.GetStats()
$results.Stats = $stats
$db.Dispose()
return $results
}
# Запуск бенчмарка
$benchmark = Test-RocksDbPerformance -Records 50000
$benchmark | ConvertTo-Json -Depth 3 | Set-Content "benchmark_results.json"
# Отображение результатов
Write-Host "`n=== РЕЗУЛЬТАТЫ БЕНЧМАРКА ===" -ForegroundColor Cyan
$benchmark.GetEnumerator() | Where-Object { $_.Key -ne "Stats" } | ForEach-Object {
Write-Host "$($_.Key):" -ForegroundColor Yellow
$_.Value.GetEnumerator() | ForEach-Object {
Write-Host " $($_.Key): $($_.Value)"
}
}
6. Интеграция с PowerShell конвейером
function Import-RocksDb {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path,
[Parameter(ValueFromPipeline)]
[PSObject]$InputObject,
[string]$ColumnFamily = "default",
[string]$KeyProperty = "Id"
)
begin {
$db = [PowerRocksDb]::new($Path)
$db.Open()
$count = 0
}
process {
foreach ($item in $InputObject) {
$key = if ($item.$KeyProperty) {
$item.$KeyProperty.ToString()
} else {
[Guid]::NewGuid().ToString()
}
$db.Put($key, $item, $ColumnFamily)
$count++
Write-Progress -Activity "Импорт в RocksDB" -Status "Импортировано: $count"
}
}
end {
Write-Host "Импортировано $count записей в $Path" -ForegroundColor Green
$db.Dispose()
}
}
function Export-RocksDb {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[string]$Path,
[string]$ColumnFamily = "default",
[string]$KeyPattern = "*"
)
$db = [PowerRocksDb]::new($Path)
$db.Open()
try {
if ($KeyPattern -eq "*") {
$items = $db.GetByPrefix("", $ColumnFamily)
} else {
$items = $db.GetByPrefix($KeyPattern, $ColumnFamily)
}
foreach ($item in $items) {
$item.Value | Add-Member -NotePropertyName "Key" -NotePropertyValue $item.Key -Force
$item.Value | Add-Member -NotePropertyName "ColumnFamily" -NotePropertyValue $item.ColumnFamily -Force
$item.Value
}
Write-Host "Экспортировано $($items.Count) записей" -ForegroundColor Green
}
finally {
$db.Dispose()
}
}
# Пример использования конвейера
Get-Process | Select-Object -First 10 -Property Name, Id, CPU, WorkingSet |
Import-RocksDb -Path "C:\Data\RocksDB\Processes" -ColumnFamily "processes"
# Экспорт данных
Export-RocksDb -Path "C:\Data\RocksDB\Processes" -ColumnFamily "processes" |
Format-Table Key, Name, Id
Архитектура RocksDbSharp
graph TB
subgraph "PowerShell Environment"
A[PowerShell 7.5 Script] --> B[PowerRocksDb Class]
B --> C[RocksDbSharp Managed API]
end
subgraph "RocksDbSharp Layer"
C --> D[P/Invoke Marshaling]
D --> E[SafeHandle Management]
E --> F[RocksDB C API Wrapper]
end
subgraph "Native RocksDB"
F --> G[rocksdb.dll]
G --> H[LSM Tree Engine]
H --> I[MemTable & SST Files]
end
subgraph "Storage"
I --> J[WAL Write-Ahead Log]
I --> K[SST Files on Disk]
H --> L[Compaction Threads]
L --> M[Leveled Compaction]
end
style A fill:#e1f5fe
style C fill:#f3e5f5
style G fill:#fff3e0Плюсы RocksDbSharp
Преимущества:
- Нативная интеграция с .NET:
- Полная поддержка .NET Standard 2.0
- Safe handles для автоматического управления памятью
- LINQ-подобные итераторы
- Простота использования:
- Объектно-ориентированный API
- Автоматическое преобразование типов
- Встроенная сериализация JSON
- Полная функциональность RocksDB:
- Все 250+ функций C API
- Поддержка Column Families
- Транзакции и снапшоты
- Настраиваемая компрессия
- Производительность:
- Минимальные накладные расходы
- Асинхронные операции
- Поддержка многопоточности
- Стабильность:
- Активная разработка от Facebook
- Регулярные обновления
- Хорошая документация
Минусы и ограничения
Недостатки:
- Зависимость от native библиотеки:
- Требует rocksdb.dll
- Проблемы с версионностью
- Нужна сборка под конкретную платформу
- Потребление памяти:
- Высокое потребление в многопоточном режиме
- MemTable требует настройки
- Block cache управляется вручную
- Сложность настройки:
- Множество параметров оптимизации
- Требуется понимание LSM-дерева
- Необходимость регулярной компактизации
- Только key-value:
- Нет SQL-запросов
- Сложные запросы требуют ручной реализации
- Ограниченные возможности индексации
- Ограниченная экосистема:
- Мало инструментов мониторинга
- Отсутствие GUI администратора
- Сложная миграция данных
Сравнение с другими подходами
| Характеристика | RocksDbSharp | P/Invoke | SQLite | ESE |
|---|---|---|---|---|
| Производительность | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| Простота использования | ⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| Память | 100-500MB | 80-400MB | 20-100MB | 50-300MB |
| Функциональность | Key-Value | Key-Value | SQL+Key-Value | ISAM+Trans |
| Надёжность | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| Документация | ⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| Поддержка PS7.5 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐ |
Оптимальные настройки для разных сценариев
class OptimizedRocksDbConfig {
static [RocksDbSharp.DbOptions]GetLoggingOptions() {
$options = [RocksDbSharp.DbOptions]::new()
$options.SetCreateIfMissing($true)
$options.SetIncreaseParallelism(2)
$options.OptimizeForPointLookup(512 * 1024 * 1024) # 512MB block cache
$options.OptimizeLevelStyleCompaction(128 * 1024 * 1024) # 128MB мемтаблица
$options.SetCompression([RocksDbSharp.Compression]::ZSTDCompression)
$options.SetBottommostCompression([RocksDbSharp.Compression]::ZSTDCompression)
$options.SetCompressionOptions("-14:1:0:0:4096:200:0")
return $options
}
static [RocksDbSharp.DbOptions]GetCacheOptions() {
$options = [RocksDbSharp.DbOptions]::new()
$options.SetCreateIfMissing($true)
$options.OptimizeForSmallDb()
$options.SetMaxOpenFiles(5000)
$options.SetWriteBufferSize(16 * 1024 * 1024) # 16MB
$options.SetMaxWriteBufferNumber(2)
$options.SetMinWriteBufferNumberToMerge(1)
$options.SetDisableAutoCompactions($true) # Для кэша не нужно
return $options
}
static [RocksDbSharp.DbOptions]GetTimeSeriesOptions() {
$options = [RocksDbSharp.DbOptions]::new()
$options.SetCreateIfMissing($true)
$options.OptimizeUniversalStyleCompaction()
$options.SetTargetFileSizeBase(128 * 1024 * 1024) # 128MB
$options.SetMaxBytesForLevelBase(512 * 1024 * 1024) # 512MB
$options.SetCompactionStyle([RocksDbSharp.Compaction]::Universal)
$options.SetCompression([RocksDbSharp.Compression]::ZSTDCompression)
return $options
}
}
Обработка ошибок и мониторинг
class MonitoredRocksDb : PowerRocksDb {
[System.Collections.Concurrent.ConcurrentDictionary[string, int]]$Metrics
[System.Diagnostics.Stopwatch]$OperationTimer
MonitoredRocksDb([string]$path) : base($path) {
$this.Metrics = [System.Collections.Concurrent.ConcurrentDictionary[string, int]]::new()
$this.OperationTimer = [System.Diagnostics.Stopwatch]::new()
}
[void]Put([string]$key, [object]$value, [string]$columnFamily = "default") {
$this.OperationTimer.Restart()
try {
[base]::Put($key, $value, $columnFamily)
$this.Metrics.AddOrUpdate("PutSuccess", 1, { param($k, $v) $v + 1 })
}
catch [RocksDbSharp.RocksDbException] {
$this.Metrics.AddOrUpdate("PutError", 1, { param($k, $v) $v + 1 })
Write-Error "Ошибка записи ключа '$key': $($_.Exception.Message)"
throw
}
finally {
$this.OperationTimer.Stop()
$latency = $this.OperationTimer.ElapsedMilliseconds
$this.Metrics.AddOrUpdate("PutLatency", $latency, {
param($k, $v) [Math]::Round(($v + $latency) / 2)
})
}
}
[hashtable]GetMetrics() {
return @{
Operations = $this.Metrics
Properties = $this.GetStats().Properties
Timestamp = Get-Date
}
}
[void]CheckHealth() {
$stats = $this.GetStats()
# Проверка использования памяти
$memUsage = [long]$stats.Properties["rocksdb.cur-size-all-mem-tables"] / 1MB
if ($memUsage -gt 512) {
Write-Warning "Высокое использование памяти: ${memUsage}MB"
}
# Проверка уровня L0 файлов
$l0Files = [int]$stats.Properties["rocksdb.num-files-at-level0"]
if ($l0Files -gt 20) {
Write-Warning "Много файлов L0 ($l0Files). Рекомендуется компактизация."
$this.Compact()
}
# Проверка ошибок
$errors = $this.Metrics.GetOrAdd("PutError", 0)
if ($errors -gt 100) {
Write-Error "Обнаружено $errors ошибок записи"
}
}
}
Рекомендации по использованию
Когда использовать RocksDbSharp:
✅ Используйте RocksDbSharp если:
- Требуется максимальная производительность key-value операций
- Нужна многопоточная обработка данных
- Работаете с временными рядами или логами
- Требуется горизонтальное масштабирование
- Готовы к ручной настройке параметров
❌ Не используйте RocksDbSharp если:
- Нужны сложные SQL запросы
- Проект небольшой с простыми требованиями
- Нет возможности настраивать параметры БД
- Требуется GUI для администрирования
Лучшие практики для PowerShell:
- Используйте Column Families для организации данных
- Настройте размеры буферов под объём данных
- Включайте сжатие для экономии места
- Регулярно делайте компактизацию для производительности
- Мониторьте метрики для своевременной оптимизации
Альтернативы в экосистеме .NET:
# Для других сценариев рассмотрите:
# 1. LiteDB - легковесная embedded NoSQL
# 2. Marten - документная БД поверх PostgreSQL
# 3. RavenDB - полноценная документная БД
# 4. EventStoreDB - для event sourcing
Заключение
RocksDbSharp — это наиболее удобный способ использования RocksDB в PowerShell 7.5, предоставляющий:
Преимущества:
- Полная интеграция с .NET через managed код
- Автоматическое управление памятью и ресурсами
- Простой и интуитивный API
- Поддержка всех функций RocksDB
Идеально подходит для:
- Высоконагруженных логгирующих систем
- Хранения временных рядов и метрик
- Кэширования больших объёмов данных
- Приложений с интенсивными операциями записи
Требует:
- Понимания архитектуры LSM-деревьев
- Настройки параметров под конкретную нагрузку
- Регулярного обслуживания (компактизация)
Для большинства задач PowerShell, где требуется максимальная производительность key-value операций, RocksDbSharp является оптимальным выбором, сочетающим мощь RocksDB с удобством .NET разработки.