EN RU

Использование RocksDbSharp в PowerShell 7.5

Что такое 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

Преимущества:

  1. Нативная интеграция с .NET:
  • Полная поддержка .NET Standard 2.0
  • Safe handles для автоматического управления памятью
  • LINQ-подобные итераторы
  1. Простота использования:
  • Объектно-ориентированный API
  • Автоматическое преобразование типов
  • Встроенная сериализация JSON
  1. Полная функциональность RocksDB:
  • Все 250+ функций C API
  • Поддержка Column Families
  • Транзакции и снапшоты
  • Настраиваемая компрессия
  1. Производительность:
  • Минимальные накладные расходы
  • Асинхронные операции
  • Поддержка многопоточности
  1. Стабильность:
  • Активная разработка от Facebook
  • Регулярные обновления
  • Хорошая документация

Минусы и ограничения

Недостатки:

  1. Зависимость от native библиотеки:
  • Требует rocksdb.dll
  • Проблемы с версионностью
  • Нужна сборка под конкретную платформу
  1. Потребление памяти:
  • Высокое потребление в многопоточном режиме
  • MemTable требует настройки
  • Block cache управляется вручную
  1. Сложность настройки:
  • Множество параметров оптимизации
  • Требуется понимание LSM-дерева
  • Необходимость регулярной компактизации
  1. Только key-value:
  • Нет SQL-запросов
  • Сложные запросы требуют ручной реализации
  • Ограниченные возможности индексации
  1. Ограниченная экосистема:
  • Мало инструментов мониторинга
  • Отсутствие GUI администратора
  • Сложная миграция данных

Сравнение с другими подходами

ХарактеристикаRocksDbSharpP/InvokeSQLiteESE
Производительность⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Простота использования⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Память100-500MB80-400MB20-100MB50-300MB
ФункциональностьKey-ValueKey-ValueSQL+Key-ValueISAM+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:

  1. Используйте Column Families для организации данных
  2. Настройте размеры буферов под объём данных
  3. Включайте сжатие для экономии места
  4. Регулярно делайте компактизацию для производительности
  5. Мониторьте метрики для своевременной оптимизации

Альтернативы в экосистеме .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 разработки.

Добавить комментарий

Разработка и продвижение сайтов webseed.ru
Прокрутить вверх