Redis → SQL Server
Key-value to relational migration from Redis to SQL Server. MigrationBridge reads Redis Hash keys matching a pattern, reconstructs rows by coercing string values to SQL types, and inserts into SQL Server tables.
Overview
MigrationBridge reconstructs relational rows from Redis Hash keys. During a prior SQL Server → Redis export, each table row was stored as a Redis Hash using the key pattern schema:table:PK_value. Each Hash field corresponds to a column name, and every value is stored as a UTF-8 string. This SOP reverses that process.
How Key-Value to Relational Reconstruction Works
MigrationBridge iterates over Redis using SCAN to discover all key prefixes matching the pattern schema:table:*. For each matching key it issues HSCAN to read all Hash fields and values. Field names map directly to SQL Server column names. String values are coerced back to their original SQL types using MigrationBridge's type inference engine (see Type Conversion Reference).
Key Pattern Convention
All keys must follow the three-part colon-delimited convention established during the export phase:
schema:table:PK_value Examples: HumanResources:Employee:1 HumanResources:Employee:2 Person:Address:10 Sales:SalesOrderHeader:43659
redis-cli SCAN 0 COUNT 10 before proceeding.TRUNCATE Before INSERT
To prevent primary key duplicate errors on re-runs, MigrationBridge issues a TRUNCATE TABLE on the target table before each batch INSERT. This means the migration is not incremental by default — it is a full replacement of the target table's data. If the target table has foreign key constraints, MigrationBridge temporarily disables them during the operation and re-enables them after.
Validated Run — AdventureWorks2019
This SOP was validated against AdventureWorks2019 on July 8, 2026: 70 key prefixes (tables), 760,836 rows migrated, zero coercion failures, zero PK violations. Total elapsed time: 4 minutes 12 seconds on a local Redis instance + SQL Server 2019 target.
Prerequisites — Redis (Source)
Driver
MigrationBridge uses the redis-py Python driver. It is bundled with the MigrationBridge installation. No separate driver installation is required when running from the packaged binary or the installed Python environment.
pip show redis # Expected: redis 4.6.x or 5.x
Connection String Formats
| Environment | Format | Notes |
|---|---|---|
| Local / on-prem (no auth) | redis://localhost:6379/0 |
DB number appended as path segment |
| Local / on-prem (with AUTH) | redis://:password@localhost:6379/0 |
Colon before password is required |
| Azure Cache for Redis | rediss://<name>.redis.cache.windows.net:6380 |
rediss:// (double-s) enforces TLS 1.2. Password = Access Key from Azure Portal. |
| AWS ElastiCache (cluster disabled) | redis://<endpoint>.cache.amazonaws.com:6379/0 |
Must run from within the same VPC. Use rediss:// if in-transit encryption is enabled. |
| AWS ElastiCache (TLS + AUTH) | rediss://:token@<endpoint>:6380/0 |
Requires ElastiCache auth token configured on the cluster |
Test Connectivity
redis-cli -h localhost -p 6379 PING # Expected: PONG redis-cli -h localhost -p 6379 DBSIZE # Expected: integer (number of keys in DB 0) # For Azure Cache (TLS): redis-cli -h myinstance.redis.cache.windows.net -p 6380 --tls -a "<access-key>" PING # For ElastiCache: redis-cli -h mycluster.xxxxxx.cache.amazonaws.com -p 6379 PING
PING returns NOAUTH Authentication required, the instance requires an AUTH password. Supply it in the connection URI or in the MigrationBridge password field. Azure Cache for Redis always requires AUTH — use the Primary Access Key from the Azure Portal under Authentication.Verify Key Naming Convention
Before starting the migration, confirm keys follow the schema:table:PK convention:
redis-cli SCAN 0 COUNT 20 # Sample output should include keys like: # HumanResources:Employee:1 # Person:Address:10 # Sales:SalesOrderHeader:43659 # Count keys for a specific prefix: redis-cli KEYS "HumanResources:Employee:*" | wc -l
table_schema.table_name:PK or flat keys without schema prefix), MigrationBridge will misinterpret the prefix and create incorrect table names. Contact the export operator to confirm the original pattern before proceeding.Required Redis Permissions
SCAN— enumerate keysHSCAN/HGETALL— read Hash fields and valuesDBSIZE— key count for assessmentTYPE— confirm key type is Hash- Read-only ACL is sufficient; MigrationBridge does not write to or delete from Redis
Prerequisites — SQL Server (Target)
Permissions
The SQL login used by MigrationBridge requires the following minimum permissions on the target database:
| Permission | Purpose | Scope |
|---|---|---|
db_owner | All operations (simplest) | Target database |
— or — CREATE TABLE | Create tables if they don't exist | Target database |
INSERT | Load rows | All target tables |
TRUNCATE TABLE | Clear table before reload | All target tables |
ALTER TABLE | Disable/re-enable FK constraints | All target tables with FK |
VIEW DATABASE STATE | Space and file group checks | Target database |
ODBC Driver
MigrationBridge requires ODBC Driver 17 for SQL Server or later. Verify installation:
# Windows — check installed ODBC drivers: odbcad32.exe (via Run dialog → Drivers tab) # Or from PowerShell: Get-OdbcDriver -Name "ODBC Driver 17 for SQL Server" # Download if missing: # https://learn.microsoft.com/en-us/sql/connect/odbc/download-odbc-driver-for-sql-server
Pre-flight Space Check
Run this query against the target instance to confirm sufficient free space before migration:
-- Pre-flight: check data file free space on target database
SELECT
DB_NAME() AS DatabaseName,
SUM(size * 8 / 1024) AS AllocatedMB,
SUM(FILEPROPERTY(name, 'SpaceUsed') * 8 / 1024) AS UsedMB,
SUM((size - FILEPROPERTY(name, 'SpaceUsed')) * 8 / 1024) AS FreeMB
FROM sys.database_files
WHERE type_desc = 'ROWS';
Table Creation
MigrationBridge will create target tables automatically if they do not exist, using the inferred column definitions from the first 100 Hash keys of each prefix. If the target tables already exist with the correct schema, MigrationBridge skips CREATE TABLE and proceeds directly to TRUNCATE + INSERT. If the target schema differs from the inferred schema, a warning is logged and the operation continues with best-effort column matching.
Configure Connections
Launch MigrationBridge and navigate to New Migration. Select Redis as Source and SQL Server as Target.
Source — Redis
Target — SQL Server
redis://host:6379/2 for DB 2.rediss:// (double-s) and port 6380. Plain redis:// on port 6379 will be rejected by Azure Cache because non-TLS access is disabled by default.Assessment
Click Run Assessment. MigrationBridge scans all keys using SCAN 0 MATCH * COUNT 500, groups them by their schema:table prefix, counts rows per prefix, samples the first 20 keys per prefix to infer column names and types, and produces a pre-migration report.
What the Assessment Checks
- Key naming convention compliance — warns if any keys do not match
schema:table:PK - Hash field consistency — checks that all keys under a prefix share the same field names
- Type coercion readiness — samples values to predict coercion outcomes
- Target table existence and schema compatibility
- Estimated INSERT batches and time based on SCAN rate
Type Coercion Rules Shown in Assessment
The assessment output includes a coercion preview for each prefix, showing the inferred SQL type for each field. The full coercion reference is in the Type Conversion Reference section. Common coercions flagged during assessment:
| Coercion | Assessment Message | Severity |
|---|---|---|
| ISO 8601 string → DATETIME2 | datetime ISO8601 coercion confirmed | OK |
| "1"/"0" → BIT | bit coercion: "1"/"0" detected | OK |
| UUID string → UNIQUEIDENTIFIER | GUID format confirmed for field [rowguid] | OK |
| Value > 4000 chars | NVARCHAR(MAX) required for field [X] | INFO |
| Ambiguous numeric | ambiguous type — defaulting to FLOAT for field [X] | WARN |
Select Key Prefixes
After assessment, the Prefix Selection panel lists all discovered key prefixes. Each prefix maps to exactly one SQL Server table. Select the prefixes to migrate. By default all prefixes are selected.
| Key Prefix | → SQL Server Table | Keys (Rows) | Hash Fields (Cols) | Status | |
|---|---|---|---|---|---|
HumanResources:Employee |
[HumanResources].[Employee] |
290 | 26 | Ready | |
Person:Address |
[Person].[Address] |
19,614 | 9 | Ready | |
Person:Person |
[Person].[Person] |
19,972 | 12 | Ready | |
Production:Product |
[Production].[Product] |
504 | 25 | Ready | |
Production:WorkOrder |
[Production].[WorkOrder] |
72,591 | 10 | Ready | |
Sales:SalesOrderHeader |
[Sales].[SalesOrderHeader] |
31,465 | 25 | Ready | |
Sales:SalesOrderDetail |
[Sales].[SalesOrderDetail] |
121,317 | 11 | Ready | |
Purchasing:PurchaseOrderDetail |
[Purchasing].[PurchaseOrderDetail] |
8,845 | 10 | Ready | |
Production:TransactionHistory |
[Production].[TransactionHistory] |
113,443 | 9 | Ready | |
| ... 61 more prefixes — 372,795 additional rows ... | |||||
Sales:). This filters the list to show only prefixes beginning with that string. You can then use Select All to select only the filtered results.Migration & Log
Click Start Migration. MigrationBridge processes each prefix sequentially. For each prefix it: (1) issues SCAN with the prefix pattern, (2) fetches Hash fields via HSCAN, (3) coerces types, (4) TRUNCATES the target table, (5) bulk-INSERTs rows in configurable batches (default: 500 rows). Progress and all events are written to the log panel in real time.
MONEY type columns is expected. Redis stores all values as strings; MigrationBridge coerces numeric strings to FLOAT. If the target table was pre-created with MONEY columns, SQL Server will implicitly convert FLOAT to MONEY on INSERT, which may cause sub-cent rounding differences. Verify financial columns after migration if precision is critical._redis_key (if present in any Hash from a prior MigrationBridge export) is automatically excluded from INSERT column lists. It will never be written to the SQL Server target table.Verify
After migration completes, run the following validation queries against the SQL Server target to confirm row counts, spot-check individual rows, and verify database integrity.
Row Count Validation — All Tables
-- Row counts for all user tables — compare against MigrationBridge summary log
SELECT
QUOTENAME(s.name) + '.' + QUOTENAME(t.name) AS TableName,
p.rows AS RowCount
FROM sys.tables t
INNER JOIN sys.schemas s ON s.schema_id = t.schema_id
INNER JOIN sys.partitions p ON p.object_id = t.object_id
AND p.index_id IN (0, 1)
ORDER BY s.name, t.name;
Spot-Check a Specific Row by Primary Key
-- Spot-check: HumanResources.Employee — BusinessEntityID = 1
SELECT
BusinessEntityID,
NationalIDNumber,
LoginID,
JobTitle,
BirthDate,
MaritalStatus,
Gender,
HireDate,
SalariedFlag,
rowguid,
ModifiedDate
FROM [HumanResources].[Employee]
WHERE BusinessEntityID = 1;
-- Spot-check: Sales.SalesOrderHeader — SalesOrderID = 43659
SELECT
SalesOrderID,
OrderDate,
ShipDate,
Status,
CustomerID,
SalesPersonID,
TerritoryID,
SubTotal,
TaxAmt,
Freight,
TotalDue,
rowguid
FROM [Sales].[SalesOrderHeader]
WHERE SalesOrderID = 43659;
Aggregate Spot-Check — Financial Columns
-- Verify financial totals in SalesOrderHeader match expected values
SELECT
COUNT(*) AS TotalOrders,
SUM(SubTotal) AS TotalSubTotal,
SUM(TaxAmt) AS TotalTax,
SUM(TotalDue) AS TotalDue,
MIN(OrderDate) AS EarliestOrder,
MAX(OrderDate) AS LatestOrder
FROM [Sales].[SalesOrderHeader];
DBCC CHECKDB — Database Integrity
-- Run after all inserts are complete; no_infomsgs suppresses informational messages
DBCC CHECKDB ('AdventureWorks2019') WITH NO_INFOMSGS, ALL_ERRORMSGS;
DBCC CHECKDB WITH PHYSICAL_ONLY for a faster structural scan, followed by a full check in a subsequent maintenance window.SUMMARY 70 prefixes | 760836 rows inserted. Any discrepancy indicates a failed INSERT batch — check the log for .err lines.Post-Migration
After successful verification, complete the following post-migration steps to restore SQL Server performance and validate data quality.
Update Statistics
TRUNCATE + bulk INSERT invalidates existing statistics. Rebuild them before the database is used in production:
-- Update statistics on all tables in the database
EXEC sp_updatestats;
-- Or update statistics on a specific high-priority table with full scan:
UPDATE STATISTICS [Sales].[SalesOrderHeader] WITH FULLSCAN;
UPDATE STATISTICS [Sales].[SalesOrderDetail] WITH FULLSCAN;
UPDATE STATISTICS [Production].[TransactionHistory] WITH FULLSCAN;
Rebuild or Add Indexes
MigrationBridge does not create non-clustered indexes. If the target tables were auto-created by MigrationBridge, only a heap (no clustered index) exists. Add indexes to restore query performance:
-- Example: add clustered PK on auto-created HumanResources.Employee
ALTER TABLE [HumanResources].[Employee]
ADD CONSTRAINT PK_Employee_BusinessEntityID
PRIMARY KEY CLUSTERED (BusinessEntityID);
-- Example: add non-clustered index on SalesOrderDetail.ProductID
CREATE NONCLUSTERED INDEX IX_SalesOrderDetail_ProductID
ON [Sales].[SalesOrderDetail] (ProductID)
INCLUDE (OrderQty, UnitPrice);
-- Rebuild all indexes (fragmentation after bulk insert):
ALTER INDEX ALL ON [Sales].[SalesOrderHeader] REBUILD;
ALTER INDEX ALL ON [Sales].[SalesOrderDetail] REBUILD;
ALTER INDEX ALL ON [schema].[table] REBUILD for all large tables before opening the database to queries.Validate Datetime Precision
If the assessment or migration log reported any datetime ISO8601 coercion warnings, validate that DATETIME2 values landed with the correct precision:
-- Check datetime precision — compare truncated vs. full value
SELECT TOP 10
BusinessEntityID,
BirthDate,
CONVERT(VARCHAR(30), BirthDate, 126) AS BirthDate_ISO,
HireDate,
CONVERT(VARCHAR(30), HireDate, 126) AS HireDate_ISO
FROM [HumanResources].[Employee]
ORDER BY BusinessEntityID;
Re-enable Foreign Key Constraints
MigrationBridge disables FK constraints before TRUNCATE and re-enables them after INSERT. Verify they are in an enabled, trusted state:
-- Verify all FK constraints are enabled and trusted
SELECT
OBJECT_NAME(fk.parent_object_id) AS TableName,
fk.name AS ConstraintName,
fk.is_disabled,
fk.is_not_trusted
FROM sys.foreign_keys fk
WHERE fk.is_disabled = 1
OR fk.is_not_trusted = 1
ORDER BY TableName;
-- Re-enable any that show as disabled (replace table/constraint names):
ALTER TABLE [schema].[table]
WITH CHECK CHECK CONSTRAINT constraint_name;
WITH CHECK CHECK CONSTRAINT (note the double keyword) forces SQL Server to re-validate all existing rows against the constraint, setting is_not_trusted = 0. This is required for the query optimizer to use the constraint for plan optimization.Final Row Count Cross-Check
-- Final validation: total rows across all user tables
SELECT
SUM(p.rows) AS TotalRows
FROM sys.tables t
INNER JOIN sys.partitions p
ON p.object_id = t.object_id
AND p.index_id IN (0, 1)
WHERE t.is_ms_shipped = 0;
-- Expected: 760836
Type Conversion Reference
MigrationBridge uses the following rules to coerce Redis string values back to SQL Server types. Rules are evaluated in the order shown — the first matching rule wins.
| Redis String Value Pattern | SQL Server Type | Example Redis Value | Example SQL Value | Notes |
|---|---|---|---|---|
| Integer string (no decimal, no dot) | INT |
"123" |
123 |
Values outside INT range promote to BIGINT |
| Numeric string with decimal point | FLOAT |
"123.45" |
123.45 |
MONEY columns receive implicit FLOAT→MONEY cast; precision warning issued |
"true" or "1" (case-insensitive) |
BIT = 1 |
"1", "True" |
1 |
Only applies when target column is BIT or when all values in field are "0"/"1"/"true"/"false" |
"false" or "0" |
BIT = 0 |
"0", "False" |
0 |
See above |
| ISO 8601 datetime string | DATETIME2 |
"1969-01-29T00:00:00" |
1969-01-29 00:00:00.0000000 |
Fractional seconds preserved up to 7 digits if present in string |
| UUID / GUID string (8-4-4-4-12 hex) | UNIQUEIDENTIFIER |
"92c4279f-1207-48a3-8448-4636514eb7e2" |
92C4279F-1207-48A3-8448-4636514EB7E2 |
Validated with regex before coercion |
Hex-encoded binary string (prefix 0x) |
VARBINARY |
"0x89504e47..." |
binary data | Only when value begins with 0x and remainder is valid hex |
| All other strings | NVARCHAR(MAX) |
"Mountain Bike" |
N'Mountain Bike' |
Default fallback; storage may exceed original column length |
| Missing Hash field (key does not have this field) | NULL |
(field absent) | NULL |
Logged as informational; column must be nullable in target table |
Known Issues
Key Naming Convention Must Match Export
. instead of :, or schema_table:PK), MigrationBridge will parse the prefix incorrectly and create wrong table names or fail to find the schema. Always verify with redis-cli SCAN 0 COUNT 10 and compare against the original export configuration before running the migration._redis_key Field Excluded Automatically
_redis_key from INSERT column lists. This is an internal tracking field written during the SQL Server → Redis export phase and has no corresponding SQL Server column. No action required — it is silently skipped.Type Coercion Failures
WARN line and substitutes NULL for that column in that row. The row is still inserted. After migration, query for unexpected NULLs in columns that should not be nullable:
SELECT COUNT(*) FROM [schema].[table] WHERE expected_column IS NULL;
Azure Cache for Redis — SSL Required
rediss:// (double-s) and port 6380. Using redis:// on port 6379 will result in a connection timeout or immediate disconnect. Non-TLS access is disabled by default and cannot be enabled on the Standard or Premium tier.AWS ElastiCache — VPC Access Required
Large Hash Fields — NVARCHAR(MAX) Inflation
NVARCHAR(MAX) to that column for the entire table. This may significantly inflate storage if the original column was a fixed-width type. Monitor with:
SELECT SUM(a.total_pages) * 8 / 1024 AS UsedMB
FROM sys.partitions p
JOIN sys.allocation_units a ON a.container_id = p.hobt_id
JOIN sys.tables t ON t.object_id = p.object_id
WHERE t.name = 'YourTable';
Foreign Key Constraint Failures on Re-run
UI Behavior During Migration
During an active migration run, the MigrationBridge UI enters a busy state to prevent conflicting operations. The following behaviors are expected and by design:
- The New Migration and Settings tabs are disabled (grayed out) while a migration is in progress.
- The Start Migration button changes to Cancel Migration. Clicking it sends a graceful stop signal — the current prefix finishes its INSERT batch before the run halts. Partial inserts for the in-progress prefix are rolled back.
- The log panel auto-scrolls to the latest line. You can pause auto-scroll by clicking anywhere in the log panel; it resumes when a new prefix starts.
- The progress bar at the bottom of the window shows prefix-level progress (e.g., 14/70). Row-level progress within a prefix updates every 5,000 rows.
- The Jobs tab remains accessible during migration and shows the active job in a RUNNING state.
- After completion or cancellation, all tabs are re-enabled and the migration status updates to COMPLETE or CANCELLED.
MigrationBridge v1.3.0 · Redis → SQL Server · User Guide · Troubleshooting