Skip to content

Latest commit

 

History

History
1908 lines (1521 loc) · 53.7 KB

File metadata and controls

1908 lines (1521 loc) · 53.7 KB

Redis Implementation Documentation

Table of Contents

  1. Overview
  2. Architecture
  3. Core Components
  4. Data Flow
  5. Implementation Details
  6. Algorithms
  7. Code Snippets
  8. File Structure
  9. Usage Examples

1. Overview

This documentation provides a comprehensive analysis of the Redis to MySQL migration implementation. The system is designed to automatically analyze Redis database structures, infer appropriate MySQL schema designs, and migrate data while preserving relationships and data integrity.

Key Features

  • Automatic Schema Analysis: Scans Redis keys and identifies patterns
  • Intelligent Type Inference: Maps Redis data types to appropriate MySQL types
  • Relationship Detection: Identifies implicit relationships between entities
  • Universal Modularity: Works with any Redis key naming pattern
  • Data Preservation: Maintains data integrity during migration

2. Architecture

System Components

The Redis to MySQL migration system consists of four main components:

┌─────────────────────┐
│   redis_data.py     │  ← Redis Data Population
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│redis_schema_analyzer│  ← Pattern Detection & Analysis
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│   redis2mysql.py    │  ← Orchestration & Data Migration
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│   sqlbuilder.py     │  ← MySQL Schema Generation
└─────────────────────┘

Component Interactions

  1. redis_data.py → Populates Redis with test data
  2. redis_schema_analyzer.py → Analyzes Redis keys and data patterns
  3. redis2mysql.py → Orchestrates the conversion process
  4. sqlbuilder.py → Builds MySQL schema based on Redis patterns

3. Core Components

3.1 Redis Data Population Module (redis_data.py)

Location: /redis_data.py
Lines: 1-138

Purpose

Populates Redis database with structured test data demonstrating various Redis data types and patterns.

Key Functions

populate_test_data() (Lines 5-88)

Populates Redis with test data including users, posts, tags, and configuration.

Algorithm:

1. Clear existing database (flushdb)
2. Create user profiles (Hash data type)
3. Create posts (Hash data type)
4. Create user-post relationships (Lists)
5. Create post tags (Sets)
6. Create configuration values (Strings)
7. Create leaderboards (Sorted Sets)
8. Create session data (Strings with TTL)

Code Snippet:

# File: redis_data.py, Lines 5-88
def populate_test_data():
    print("Populating Redis with test data...")
    
    # Clear existing data first (optional)
    r.flushdb()
    print("\nCleared existing data")
    
    # USERS (Hash data type) - like user profiles
    print("\nCreating user profiles...")
    r.hset('user:1', mapping={
        'name': 'Alice Johnson', 
        'email': 'alice@example.com', 
        'age': '25',
        'city': 'New York'
    })
    
    r.hset('user:2', mapping={
        'name': 'Bob Smith', 
        'email': 'bob@example.com', 
        'age': '30',
        'city': 'Los Angeles'
    })
    
    # POSTS (Hash data type) - blog posts or content
    r.hset('post:101', mapping={
        'title': 'Getting Started with Redis', 
        'author_id': '1', 
        'content': 'Redis is a powerful in-memory database...',
        'created_at': '2024-01-15',
        'tags': 'redis,database,tutorial'
    })
    
    # USER POSTS (Lists) - which posts belong to which user
    r.lpush('user:1:posts', '101', '103')
    
    # TAGS (Sets) - unique tags for each post
    r.sadd('tags:101', 'redis', 'database', 'tutorial', 'beginner')
    
    # SIMPLE STRINGS - counters, settings, etc.
    r.set('site:visitor_count', '1250')
    
    # SORTED SETS - leaderboards, rankings
    r.zadd('leaderboard:posts', {'user:1': 2, 'user:2': 1, 'user:3': 0})
    
    # USER SESSIONS (Strings with expiration)
    r.setex('session:user1_abc123', 3600, 'alice_session_data')

Redis Data Types Used:

  • Hash (HSET): Structured entity data (users, posts)
  • List (LPUSH): Ordered relationships (user posts)
  • Set (SADD): Unique collections (tags)
  • String (SET/SETEX): Simple key-value pairs, counters, sessions
  • Sorted Set (ZADD): Ranked data (leaderboards)
verify_redis_data() (Lines 90-134)

Verifies and displays all data created in Redis.

Algorithm:

1. Retrieve all keys using KEYS *
2. For each key:
   a. Get key type using TYPE command
   b. Read data based on type:
      - string: GET
      - hash: HGETALL
      - list: LRANGE
      - set: SMEMBERS
      - zset: ZRANGE with scores
   c. Display formatted output

Code Snippet:

# File: redis_data.py, Lines 90-134
def verify_redis_data():
    print("\n" + "="*60)
    print("VERIFYING REDIS DATA - COMPLETE DATABASE DUMP")
    print("="*60)
    
    keys = r.keys('*')
    print(f"Total keys found: {len(keys)}\n")
    
    for key in sorted(keys):
        key_str = key.decode('utf-8')
        key_type = r.type(key).decode('utf-8')
        
        print(f"KEY: {key_str}")
        print(f"TYPE: {key_type}")
        
        try:
            if key_type == 'string':
                value = r.get(key).decode('utf-8')
                print(f"VALUE: {value}")
            
            elif key_type == 'hash':
                value = {k.decode('utf-8'): v.decode('utf-8') 
                        for k, v in r.hgetall(key).items()}
                print(f"HASH:")
                for k, v in value.items():
                    print(f"    {k}: {v}")
            
            elif key_type == 'list':
                value = [item.decode('utf-8') for item in r.lrange(key, 0, -1)]
                print(f"LIST: {value}")
            
            elif key_type == 'set':
                value = [item.decode('utf-8') for item in r.smembers(key)]
                print(f"SET: {value}")
            
            elif key_type == 'zset':
                value = [(item.decode('utf-8'), score) 
                        for item, score in r.zrange(key, 0, -1, withscores=True)]
                print(f"SORTED SET: {value}")
                
        except Exception as e:
            print(f"ERROR reading {key_str}: {e}")

3.2 Redis Schema Analyzer Module (redis_schema_analyzer.py)

Location: /redis_schema_analyzer.py
Lines: 1-85

Purpose

Analyzes Redis database structure to identify key patterns, data types, and sample data for schema inference.

Class: RedisSchemaAnalyzer

Constructor __init__() (Lines 6-10)

Initializes the analyzer with Redis connection and data structures.

Code Snippet:

# File: redis_schema_analyzer.py, Lines 6-10
def __init__(self):
    self.r = redis.Redis(host='localhost', port=6379, db=0)  # local server
    self.key_patterns = defaultdict(list)  # dictionary for keys
    self.data_types = defaultdict(list)    # dictionary for values
    self.sample_data = {}

Data Structures:

  • key_patterns: Maps pattern → list of matching keys
  • data_types: Maps Redis type → list of keys
  • sample_data: Maps key → sampled data value
analyze_database() (Lines 12-28)

Main entry point for database analysis.

Algorithm:

1. Retrieve all keys from Redis
2. For each key:
   a. Decode byte string to UTF-8
   b. Get Redis data type
   c. Analyze key pattern
   d. Categorize by data type
   e. Sample the actual data
3. Print analysis results

Code Snippet:

# File: redis_schema_analyzer.py, Lines 12-28
def analyze_database(self):
    print("---------- ANALYZING REDIS DB ----------")
    
    # getting all keys
    keys = self.r.keys('*')
    print(f"\nFound {len(keys)} keys")
    
    for key in keys:
        key_str = key.decode('utf-8')  # converting redis byte string to python string
        key_type = self.r.type(key).decode('utf-8')  # redis data type
        
        self._analyze_key_pattern(key_str)
        self.data_types[key_type].append(key_str)
        self._sample_key_data(key_str, key_type)
    
    self._print_analysis()
_analyze_key_pattern() (Lines 30-37)

Extracts and generalizes key patterns by replacing numeric IDs with wildcards.

Pattern Recognition Algorithm:

1. Split key by colon delimiter
2. For each part:
   a. If part is numeric → replace with '*'
   b. Otherwise → keep as-is
3. Join parts back with colons
4. Add key to pattern group

Examples:

  • user:1 → user:*
  • user:123:profile → user:*:profile
  • post:101 → post:*
  • user:2:posts → user:*:posts

Code Snippet:

# File: redis_schema_analyzer.py, Lines 30-37
def _analyze_key_pattern(self, key):
    # splitting keys such that a key like user:123:profile is stored as
    # ['user','123','profile']
    # also replacing all numbers with * to create a pattern
    parts = key.split(':')
    if len(parts) > 1:
        pattern = ':'.join(['*' if part.isdigit() else part for part in parts])
        self.key_patterns[pattern].append(key)
_sample_key_data() (Lines 39-62)

Retrieves actual data from Redis based on the key's data type.

Type-Specific Sampling Algorithm:

For each Redis data type:
  - string:  Use GET command
  - hash:    Use HGETALL (all fields)
  - list:    Use LRANGE 0 4 (first 5 items)
  - set:     Use SMEMBERS (up to 5 items)
  - zset:    Use ZRANGE with scores

Code Snippet:

# File: redis_schema_analyzer.py, Lines 39-62
def _sample_key_data(self, key, key_type):
    try:
        # strings use get command
        if key_type == 'string':
            self.sample_data[key] = self.r.get(key).decode('utf-8')
        
        # hash uses hgetall command
        elif key_type == 'hash':
            self.sample_data[key] = {k.decode('utf-8'): v.decode('utf-8') 
                                   for k, v in self.r.hgetall(key).items()}
        
        # lists use lrange
        elif key_type == 'list':
            self.sample_data[key] = [item.decode('utf-8') 
                                   for item in self.r.lrange(key, 0, 4)]
        
        # sets use smembers
        elif key_type == 'set':
            self.sample_data[key] = [item.decode('utf-8') 
                                   for item in list(self.r.smembers(key))[:5]]
    
    except Exception as e:
        self.sample_data[key] = f"Error sampling: {e}"
_print_analysis() (Lines 64-81)

Displays formatted analysis results.

Output Format:

KEY PATTERNS:
  pattern → count
  Examples: [key1, key2, key3]

DATA TYPES:
  type: count

SAMPLE DATA:
  key: data

3.3 Redis to MySQL Converter Module (redis2mysql.py)

Location: /redis2mysql.py
Lines: 1-323

Purpose

Orchestrates the entire conversion process from Redis to MySQL, handling connection management, schema creation, and data migration.

Class: RedisToMySQLConverter

Constructor __init__() (Lines 9-18)

Initializes converter with database connections and analyzer.

Code Snippet:

# File: redis2mysql.py, Lines 9-18
def __init__(self, mysql_config):
    self.redis_client = redis.Redis(host='localhost', port=6379, db=0)
    self.mysql_config = mysql_config
    self.mysql_conn = None 
    self.cursor = None
    self.analyzer = RedisSchemaAnalyzer()
    self.key_patterns = defaultdict(list)
    self.data_types = defaultdict(list)
    self.schema_mapping = {}
    self.schema_builder = None
connect_mysql() (Lines 20-46)

Establishes MySQL connection and ensures database exists.

Connection Algorithm:

1. Connect to MySQL server (without database)
2. Check if target database exists
3. If not exists → CREATE DATABASE
4. USE database
5. Initialize MySQLSchemaBuilder

Code Snippet:

# File: redis2mysql.py, Lines 20-46
def connect_mysql(self):
    try:
        config_without_db = self.mysql_config.copy()
        database_name = config_without_db.pop('database', 'redis_converted')
        
        self.mysql_conn = mysql.connector.connect(**config_without_db)
        self.cursor = self.mysql_conn.cursor()
        print("Connected to MySQL!")
        
        # Check if database exists, if not create it
        self.cursor.execute("SHOW DATABASES")
        existing_dbs = [db[0].lower() for db in self.cursor.fetchall()]
        
        if database_name.lower() not in existing_dbs:
            self.cursor.execute(f"CREATE DATABASE {database_name}")
            print(f"Created new database: {database_name}")
        else:
            print(f"Using existing database: {database_name}")
            
        self.cursor.execute(f"USE {database_name}")
        print(f"Using database: {database_name}")
        
        self.schema_builder = MySQLSchemaBuilder(self.cursor)
    except Exception as e:
        print(f"MySQL connection failed: {e}")
        return False
    return True
analyze_redis_schema() (Lines 48-53)

Delegates to RedisSchemaAnalyzer for pattern detection.

Code Snippet:

# File: redis2mysql.py, Lines 48-53
def analyze_redis_schema(self):
    self.analyzer.analyze_database()
    self.key_patterns = self.analyzer.key_patterns
    self.data_types = self.analyzer.data_types
    print("Analysis complete!")
    return self.key_patterns
create_mysql_schema() (Lines 55-64)

Creates MySQL tables based on Redis patterns.

Code Snippet:

# File: redis2mysql.py, Lines 55-64
def create_mysql_schema(self):
    if not self.schema_builder:
        print("Schema builder not initialized!")
        return False
    
    self.schema_builder.build_schema_from_patterns(
        self.key_patterns, 
        self.analyzer.sample_data
    )
    return True
migrate_data() (Lines 66-93)

Main data migration orchestrator.

Migration Algorithm:

1. For each key pattern:
   a. If entity pattern → migrate entity data
   b. If relationship pattern → migrate relationship data
2. Migrate tags sets (special case)
3. Migrate simple configuration keys
4. Commit all changes

Code Snippet:

# File: redis2mysql.py, Lines 66-93
def migrate_data(self):
    print("\n" + "="*60)
    print("MIGRATING DATA FROM REDIS TO MYSQL")
    print("="*60)
    
    redis_keys = self.redis_client.keys('*')
    print(f"Found {len(redis_keys)} keys to migrate")
    
    # Migrate entity and relationship data
    for pattern, keys in self.key_patterns.items():
        if self.schema_builder._is_entity_pattern(pattern):
            self._migrate_entity_data(pattern, keys)
        elif self.schema_builder._is_relationship_pattern(pattern):
            self._migrate_relationship_data(pattern, keys)
    
    # Migrate tags sets
    self._migrate_tags_sets()
    
    # Migrate simple configuration keys
    self._migrate_simple_keys()
    
    self.mysql_conn.commit()
    print(f"\nMigration complete! Migrated {len(redis_keys)} keys")
    return True
_migrate_entity_data() (Lines 95-115)

Migrates Hash-type entity data to MySQL tables.

Entity Migration Algorithm:

1. Extract entity name from pattern (e.g., "user" from "user:*")
2. Determine table name using pluralization
3. For each key in pattern:
   a. Extract Redis ID from key
   b. Get hash data from sample_data
   c. Build INSERT statement with columns and values
   d. Execute SQL INSERT

Code Snippet:

# File: redis2mysql.py, Lines 95-115
def _migrate_entity_data(self, pattern, keys):
    entity_name = pattern.split(':')[0]
    table_name = self.schema_builder._pluralize_table_name(entity_name)
    
    print(f"\n Migrating {len(keys)} {entity_name} entities...")
    
    for key in keys:
        redis_data = self.analyzer.sample_data.get(key)
        if isinstance(redis_data, dict):
            redis_id = key.split(':')[1]
            columns = list(redis_data.keys())
            values = list(redis_data.values())
            placeholders = ', '.join(['%s'] * len(values))
            columns_str = ', '.join(columns)
            insert_sql = f"INSERT INTO {table_name} ({columns_str}) VALUES ({placeholders})"
            
            try:
                self.cursor.execute(insert_sql, values)
                print(f"   Migrated {key}")
            except Exception as e:
                print(f"   Error migrating {key}: {e}")
_migrate_relationship_data() (Lines 117-142)

Migrates List-type relationship data to junction tables.

Relationship Migration Algorithm:

1. Parse pattern to extract entity and relationship names
2. Build junction table name
3. For each key:
   a. Extract entity ID
   b. Get list of related IDs
   c. For each related ID with position:
      - INSERT into junction table with position_order

Code Snippet:

# File: redis2mysql.py, Lines 117-142
def _migrate_relationship_data(self, pattern, keys):
    parts = pattern.split(':')
    if len(parts) == 3:
        entity1 = parts[0]
        relationship = parts[2]
        table_name = f"{entity1}_{relationship}"
        
        print(f"\n Migrating {len(keys)} {relationship} relationships...")
        
        for key in keys:
            entity_id = key.split(':')[1]
            redis_data = self.analyzer.sample_data.get(key)
            
            if isinstance(redis_data, list):
                for position, related_id in enumerate(redis_data):
                    insert_sql = f"""
                    INSERT INTO {table_name} 
                    ({entity1}_id, {relationship.rstrip('s')}_id, position_order) 
                    VALUES (%s, %s, %s)
                    """
                    try:
                        self.cursor.execute(insert_sql, (entity_id, related_id, position))
                    except Exception as e:
                        print(f"   Error migrating {key}: {e}")
                        
            print(f"   Migrated {key}")
_migrate_tags_sets() (Lines 144-161)

Special handler for tag sets migration.

Tags Migration Algorithm:

1. Find all keys starting with "tags:"
2. For each tag key:
   a. Extract post_id
   b. Get set of tags
   c. For each tag:
      - INSERT INTO tags table with IGNORE (avoid duplicates)

Code Snippet:

# File: redis2mysql.py, Lines 144-161
def _migrate_tags_sets(self):
    print("\nMigrating tags sets to tags table...")
    tag_keys = [k for k in self.analyzer.sample_data.keys() if k.startswith('tags:')]
    count = 0
    
    for key in tag_keys:
        post_id = key.split(':')[1]
        tags = self.analyzer.sample_data.get(key, [])
        
        for tag in tags:
            insert_sql = "INSERT IGNORE INTO tags (post_id, tag) VALUES (%s, %s)"
            try:
                self.cursor.execute(insert_sql, (post_id, tag))
                count += 1
            except Exception as e:
                print(f"   Error migrating tag '{tag}' for post {post_id}: {e}")
    
    print(f"   Migrated {count} tags to tags table.")
_migrate_simple_keys() (Lines 163-193)

Migrates simple key-value pairs to configuration table.

Configuration Migration Algorithm:

1. Get simple keys from schema builder
2. For each configuration key:
   a. Read string value from Redis
   b. Determine data type (boolean/integer/float/string)
   c. INSERT INTO configuration table

Code Snippet:

# File: redis2mysql.py, Lines 163-193
def _migrate_simple_keys(self):
    """Migrate simple key-value pairs to configuration table."""
    if 'configuration' not in self.schema_builder.created_tables:
        return
        
    config_keys = self.schema_builder.simple_keys
    
    if config_keys:
        print(f"\nMigrating {len(config_keys)} configuration entries...")
        
        for key in config_keys:
            key_type = self.redis_client.type(key).decode('utf-8')
            if key_type == 'string':
                value = self.redis_client.get(key)
                if value:
                    value_str = value.decode('utf-8')
                    data_type = self._determine_data_type(value_str)
                    
                    insert_sql = """
                    INSERT IGNORE INTO configuration (config_key, config_value, data_type) 
                    VALUES (%s, %s, %s)
                    """
                    try:
                        self.cursor.execute(insert_sql, (key, value_str, data_type))
                        print(f"   Migrated {key}")
                    except Exception as e:
                        print(f"   Error migrating {key}: {e}")
_determine_data_type() (Lines 195-206)

Infers data type for configuration values.

Type Detection Logic:

If value is "true" or "false" → boolean
Else if value is all digits → integer
Else if value is valid float → float
Else → string
convert() (Lines 208-219)

Main conversion orchestrator method.

Conversion Flow:

1. Connect to MySQL
2. Analyze Redis schema
3. Create MySQL schema
4. Migrate data

3.4 SQL Schema Builder Module (sqlbuilder.py)

Location: /sqlbuilder.py
Lines: 1-473

Purpose

Dynamically generates MySQL table schemas based on Redis key patterns and data structures.

Class: MySQLSchemaBuilder

Constructor __init__() (Lines 18-23)

Initializes schema builder with tracking structures.

Code Snippet:

# File: sqlbuilder.py, Lines 18-23
def __init__(self, cursor):
    """Initialize with MySQL cursor and tracking structures."""
    self.cursor = cursor
    self.created_tables = set()
    self.table_columns = {}  # Track columns for each table
    self.simple_keys = []    # Track simple key-value pairs for config table
build_schema_from_patterns() (Lines 36-67)

Main schema building orchestrator using three-pass approach.

Three-Pass Schema Building Algorithm:

PASS 1: Entity Tables
  - Identify entity patterns (entity:*)
  - Create tables for each entity type
  - Infer column types from hash data

PASS 2: Relationship Tables
  - Identify relationship patterns (entity:*:relationship)
  - Create junction tables
  - Add foreign key references

PASS 3: Configuration Table
  - Collect simple keys
  - Create configuration table for misc data

Code Snippet:

# File: sqlbuilder.py, Lines 36-67
def build_schema_from_patterns(self, key_patterns, sample_data):
    """
    Main method to build schema from Redis patterns.
    Two-pass approach: entities first, then relationships.
    """
    print("\n" + "="*60)
    print("BUILDING MySQL SCHEMA FROM REDIS PATTERNS")
    print("="*60)
    
    # Collect simple keys for config table
    self._collect_simple_keys(key_patterns)
    
    # First pass: Create entity tables
    print("\nPASS 1: Creating entity tables...")
    for pattern, keys in key_patterns.items():
        if self._is_entity_pattern(pattern):
            self._create_entity_table(pattern, keys, sample_data)
    
    # Second pass: Create relationship tables
    print("\nPASS 2: Creating relationship tables...")
    for pattern, keys in key_patterns.items():
        if self._is_relationship_pattern(pattern):
            self._create_relationship_table(pattern, keys, sample_data)
    
    # Third pass: Create config table for simple keys
    if self.simple_keys:
        print("\nPASS 3: Creating configuration table...")
        self._create_config_table()
    
    print(f"\nSchema creation complete! Created {len(self.created_tables)} tables:")
    for table in sorted(self.created_tables):
        print(f"   {table}")
_is_entity_pattern() (Lines 77-81)

Determines if a pattern represents an entity.

Entity Pattern Recognition:

Pattern is entity if:
  - Has exactly 2 parts separated by ':'
  - Second part is '*'
  
Examples:
  ✓ user:*
  ✓ post:*
  ✗ user:*:posts (relationship)
  ✗ config:setting (no wildcard)

Code Snippet:

# File: sqlbuilder.py, Lines 77-81
def _is_entity_pattern(self, pattern):
    """Check if pattern is single-level entity (user:*, post:*)."""
    parts = pattern.split(':')
    # Single level with ID: entity:*
    return len(parts) == 2 and parts[1] == '*'
_is_relationship_pattern() (Lines 83-87)

Determines if a pattern represents a relationship.

Relationship Pattern Recognition:

Pattern is relationship if:
  - Has 3 or more parts
  - Contains '*' wildcard
  
Examples:
  ✓ user:*:posts
  ✓ user:*:friends
  ✗ user:* (entity)
  ✗ config:setting (simple key)
_create_entity_table() (Lines 89-115)

Creates a table for entity patterns.

Entity Table Creation Algorithm:

1. Extract entity name from pattern
2. Pluralize for table name
3. Handle special cases (tags)
4. Get sample data for first key
5. If hash data → create from hash structure
6. Otherwise → create basic table

Code Snippet:

# File: sqlbuilder.py, Lines 89-115
def _create_entity_table(self, pattern, keys, sample_data):
    """Create table for entity pattern."""
    entity_name = pattern.split(':')[0]
    table_name = self._pluralize_table_name(entity_name)
    
    print(f"    Creating entity table: {table_name}")
    
    # Special handling for tags pattern
    if entity_name == 'tags':
        self._create_tags_table(table_name)
        return
    
    # Get sample data from first key to determine structure
    sample_key = keys[0] if keys else None
    if not sample_key or sample_key not in sample_data:
        print(f"     No sample data for {pattern}, creating basic table")
        self._create_basic_table(table_name, entity_name)
        return
    
    sample = sample_data[sample_key]
    
    # Handle different Redis data types
    if isinstance(sample, dict):  # Hash data
        self._create_table_from_hash_data(table_name, sample, entity_name)
    else:
        print(f"     Non-hash entity data for {pattern}, creating basic table")
        self._create_basic_table(table_name, entity_name)
_create_table_from_hash_data() (Lines 142-174)

Generates table schema from Redis hash structure.

Hash-to-Table Conversion Algorithm:

1. Add primary key column (entity_id INT AUTO_INCREMENT)
2. For each hash field:
   a. Infer SQL type from field name and value
   b. Escape reserved words
   c. Add column definition
3. Add updated_at timestamp if not present
4. Execute CREATE TABLE

Code Snippet:

# File: sqlbuilder.py, Lines 142-174
def _create_table_from_hash_data(self, table_name, hash_data, entity_name):
    """Create SQL table based on Redis hash structure."""
    if table_name in self.created_tables:
        return
    
    columns = []
    columns.append(f"{entity_name}_id INT PRIMARY KEY AUTO_INCREMENT")
    
    # Analyze each field in the hash
    for field_name, field_value in hash_data.items():
        sql_type = self._infer_sql_type(field_name, field_value)
        # Escape reserved words
        safe_field_name = self._escape_reserved_word(field_name)
        columns.append(f"{safe_field_name} {sql_type}")
    
    # Add updated timestamp if not present
    if 'updated_at' not in hash_data:
        columns.append("updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")
    
    column_separator = ',\n            '
    create_sql = f"""
    CREATE TABLE {table_name} (
        {column_separator.join(columns)}
    )
    """
    
    try:
        self.cursor.execute(create_sql)
        self.created_tables.add(table_name)
        self.table_columns[table_name] = [col.split()[0] for col in columns]
        print(f"   Created table: {table_name} with {len(columns)} columns")
    except Exception as e:
        print(f"   Error creating table {table_name}: {e}")
_infer_sql_type() (Lines 207-270)

Intelligently maps Redis field data to appropriate MySQL types.

Type Inference Algorithm:

Analyze field_name and field_value:

1. ID fields → INT or VARCHAR(50)
2. Email fields → VARCHAR(255)
3. Phone fields → VARCHAR(20)
4. Date/time fields → DATETIME
5. Price/money fields → DECIMAL(10,2)
6. Numeric values:
   - < 128 → TINYINT
   - < 32768 → SMALLINT
   - < 2^31 → INT
   - >= 2^31 → BIGINT
7. Float values → FLOAT
8. URLs → TEXT
9. Text by length:
   - <= 50 → VARCHAR(100)
   - <= 255 → VARCHAR(500)
   - > 255 → TEXT

Code Snippet:

# File: sqlbuilder.py, Lines 207-270
def _infer_sql_type(self, field_name, field_value):
    """Intelligently map Redis data to SQL types."""
    if not field_value:
        return "VARCHAR(255)"
    
    value_str = str(field_value).strip()
    
    # ID fields
    if field_name.endswith('_id') or field_name == 'id':
        if value_str.isdigit():
            return "INT"
        else:
            return "VARCHAR(50)"
    
    # Email fields
    if 'email' in field_name.lower() or self._looks_like_email(value_str):
        return "VARCHAR(255)"
    
    # Phone fields
    if 'phone' in field_name.lower():
        return "VARCHAR(20)"
    
    # Date fields
    if any(date_word in field_name.lower() for date_word in ['date', 'time', 'created', 'updated']) or self._looks_like_date(value_str):
        return "DATETIME"
    
    # Price/money fields
    if any(money_word in field_name.lower() for money_word in ['price', 'cost', 'amount', 'total']):
        try:
            float(value_str)
            return "DECIMAL(10,2)"
        except ValueError:
            pass
    
    # Numeric fields
    if value_str.isdigit():
        num = int(value_str)
        if num < 128:
            return "TINYINT"
        elif num < 32768:
            return "SMALLINT"
        elif num < 2147483648:
            return "INT"
        else:
            return "BIGINT"
    
    # Float fields
    try:
        float(value_str)
        return "FLOAT"
    except ValueError:
        pass
    
    # URL fields
    if self._looks_like_url(value_str):
        return "TEXT"
    
    # Text length-based decisions
    if len(value_str) <= 50:
        return "VARCHAR(100)"
    elif len(value_str) <= 255:
        return "VARCHAR(500)"
    else:
        return "TEXT"
_create_relationship_table() (Lines 292-309)

Creates junction tables for relationships.

Relationship Table Creation Algorithm:

1. Parse pattern (e.g., "user:*:posts")
2. Extract entity1 and relationship name
3. Check sample data type:
   - List → create with position tracking
   - Set → create without position
4. Generate appropriate junction table
_create_list_relationship_table() (Lines 311-338)

Creates junction table for list-based relationships (ordered).

List Relationship Table Structure:

CREATE TABLE entity_relationship (
    id INT PRIMARY KEY AUTO_INCREMENT,
    entity_id INT NOT NULL,
    related_id INT NOT NULL,
    position_order INT,           -- Preserves list order
    created_at TIMESTAMP,
    INDEX idx_entity_id (entity_id),
    INDEX idx_related_id (related_id)
)
_pluralize_table_name() (Lines 391-403)

Converts entity names to plural table names following English grammar rules.

Pluralization Rules:

Special cases:
  - tags → tags (already plural)
  - tag → tags

Rules:
  - ends with 'y' → replace with 'ies' (category → categories)
  - ends with 's','sh','ch','x','z' → add 'es' (class → classes)
  - default → add 's' (user → users)

Code Snippet:

# File: sqlbuilder.py, Lines 391-403
def _pluralize_table_name(self, entity_name):
    """Convert entity name to plural table name."""
    # Special cases for better naming
    if entity_name == 'tags':  # Already plural
        return 'tags'
    elif entity_name == 'tag':
        return 'tags'
    elif entity_name.endswith('y'):
        return entity_name[:-1] + 'ies'  # category → categories
    elif entity_name.endswith(('s', 'sh', 'ch', 'x', 'z')):
        return entity_name + 'es'  # class → classes
    else:
        return entity_name + 's'  # user → users

4. Data Flow

Complete Migration Flow

┌─────────────────────────────────────────┐
│ STEP 1: Redis Data Population           │
│ File: redis_data.py                      │
│                                          │
│ - Clear Redis database                  │
│ - Create structured test data:          │
│   • Users (Hash)                         │
│   • Posts (Hash)                         │
│   • User-Post relationships (List)      │
│   • Tags (Set)                           │
│   • Configuration (String)               │
│   • Leaderboards (Sorted Set)           │
│   • Sessions (String with TTL)          │
└────────────┬────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────┐
│ STEP 2: Schema Analysis                 │
│ File: redis_schema_analyzer.py           │
│                                          │
│ - Scan all Redis keys                   │
│ - Pattern detection (user:*)            │
│ - Data type categorization              │
│ - Sample data collection                │
│                                          │
│ Output: key_patterns, data_types,       │
│         sample_data dictionaries        │
└────────────┬────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────┐
│ STEP 3: MySQL Connection                │
│ File: redis2mysql.py                     │
│                                          │
│ - Connect to MySQL server                │
│ - Create database if needed             │
│ - Initialize schema builder             │
└────────────┬────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────┐
│ STEP 4: Schema Generation               │
│ File: sqlbuilder.py                      │
│                                          │
│ PASS 1: Entity Tables                   │
│ - Detect entity patterns                │
│ - Infer column types                    │
│ - Create tables                         │
│                                          │
│ PASS 2: Relationship Tables             │
│ - Detect relationship patterns          │
│ - Create junction tables                │
│                                          │
│ PASS 3: Configuration Table             │
│ - Collect simple keys                   │
│ - Create config table                   │
└────────────┬────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────┐
│ STEP 5: Data Migration                  │
│ File: redis2mysql.py                     │
│                                          │
│ - Migrate entity data                   │
│ - Migrate relationship data             │
│ - Migrate tags                          │
│ - Migrate configuration                 │
│ - Commit transaction                    │
└─────────────────────────────────────────┘

5. Implementation Details

5.1 Key Pattern Recognition

The system uses colon-separated patterns to identify entity types and relationships:

Pattern Types:

  1. Entity Pattern: entity:id → entity:*

    • Example: user:1, user:2 → pattern user:*
    • Maps to: users table
  2. Relationship Pattern: entity:id:relationship → entity:*:relationship

    • Example: user:1:posts, user:2:posts → pattern user:*:posts
    • Maps to: user_posts junction table
  3. Simple Keys: No colon or non-standard pattern

    • Example: site:visitor_count, config:max_posts
    • Maps to: configuration table

5.2 Type Inference Strategy

The _infer_sql_type() method uses multiple heuristics:

  1. Name-based inference: Field names suggest types

    • email → VARCHAR(255)
    • phone → VARCHAR(20)
    • *_id → INT
    • price → DECIMAL(10,2)
  2. Content-based inference: Value patterns

    • Looks like email → VARCHAR(255)
    • Looks like URL → TEXT
    • Looks like date → DATETIME
  3. Size-based inference: Numeric ranges

    • 0-127 → TINYINT
    • 128-32767 → SMALLINT
    • 32768-2^31-1 → INT
    • = 2^31 → BIGINT

  4. Length-based inference: String length

    • <= 50 chars → VARCHAR(100)
    • 51-255 chars → VARCHAR(500)
    • 255 chars → TEXT

5.3 Relationship Handling

List Relationships (Ordered):

  • Redis: LPUSH user:1:posts 101 103
  • MySQL: Junction table with position_order column
  • Preserves order of elements

Set Relationships (Unordered):

  • Redis: SADD tags:101 redis database
  • MySQL: Junction table without position
  • Unique constraints prevent duplicates

5.4 Data Type Mappings

Redis Type Redis Example MySQL Representation
Hash user:1 → {name, email, age} Table row with columns
List user:1:posts → [101, 103] Junction table rows
Set tags:101 → {redis, database} Junction table rows
String site:count → "1250" Configuration table row
Sorted Set leaderboard → {user:1:2} Specialized table

6. Algorithms

6.1 Pattern Detection Algorithm

def detect_pattern(key):
    """
    Input: Redis key (e.g., "user:123:profile")
    Output: Pattern (e.g., "user:*:profile")
    
    Algorithm:
    1. Split key by ':'
    2. For each part:
       a. If part is numeric (isdigit()) → replace with '*'
       b. Otherwise → keep original
    3. Join parts back with ':'
    4. Return pattern
    
    Time Complexity: O(n) where n = number of parts in key
    Space Complexity: O(n)
    """
    parts = key.split(':')
    pattern_parts = []
    for part in parts:
        if part.isdigit():
            pattern_parts.append('*')
        else:
            pattern_parts.append(part)
    return ':'.join(pattern_parts)

6.2 Type Inference Algorithm

def infer_type(field_name, field_value):
    """
    Input: Field name and value
    Output: MySQL column type
    
    Algorithm:
    1. Priority checks (in order):
       a. Check field name for keywords
       b. Check value format/pattern
       c. Check numeric range
       d. Check string length
    2. Return most specific type found
    
    Time Complexity: O(1) - constant checks
    Space Complexity: O(1)
    """
    # Priority 1: Name-based
    if '_id' in field_name:
        return 'INT' if is_numeric(value) else 'VARCHAR(50)'
    if 'email' in field_name:
        return 'VARCHAR(255)'
    
    # Priority 2: Pattern-based
    if looks_like_date(value):
        return 'DATETIME'
    if looks_like_url(value):
        return 'TEXT'
    
    # Priority 3: Numeric range
    if is_integer(value):
        return determine_int_type(value)
    
    # Priority 4: Length-based
    return determine_varchar_size(value)

6.3 Schema Building Algorithm

def build_schema(key_patterns, sample_data):
    """
    Three-pass schema generation algorithm
    
    PASS 1: Entity Tables
    -----------------------
    For each pattern in patterns:
        If is_entity_pattern(pattern):
            entity_name = extract_entity(pattern)
            sample = get_first_sample(pattern)
            if sample is Hash:
                columns = infer_columns_from_hash(sample)
                create_table(entity_name, columns)
    
    PASS 2: Relationship Tables
    ----------------------------
    For each pattern in patterns:
        If is_relationship_pattern(pattern):
            entity, relationship = parse_relationship(pattern)
            if sample is List:
                create_junction_table_with_order(entity, relationship)
            elif sample is Set:
                create_junction_table_unique(entity, relationship)
    
    PASS 3: Configuration Table
    -----------------------------
    simple_keys = find_non_pattern_keys(patterns)
    if simple_keys exists:
        create_configuration_table()
    
    Time Complexity: O(n*m) where n=patterns, m=avg keys per pattern
    Space Complexity: O(n) for tracking created tables
    """

6.4 Data Migration Algorithm

def migrate_data(patterns, sample_data):
    """
    Migrate data from Redis to MySQL
    
    For each pattern, keys_list in patterns:
        If is_entity(pattern):
            table = get_table_name(pattern)
            For each key in keys_list:
                data = get_redis_hash(key)
                insert_into_table(table, data)
        
        Elif is_relationship(pattern):
            junction_table = get_junction_table(pattern)
            For each key in keys_list:
                relations = get_redis_list(key)
                For position, related_id in enumerate(relations):
                    insert_into_junction(junction_table, key_id, related_id, position)
    
    # Special cases
    migrate_tags()
    migrate_configuration()
    
    commit_transaction()
    
    Time Complexity: O(k) where k = total number of keys
    Space Complexity: O(1) - streaming migration
    """

7. Code Snippets by Function

7.1 Redis Connection Setup

# File: redis_data.py, Lines 1-3
import redis

r = redis.Redis(host='localhost', port=6379, db=0)

7.2 Creating Hash Data (Entities)

# File: redis_data.py, Lines 14-19
r.hset('user:1', mapping={
    'name': 'Alice Johnson', 
    'email': 'alice@example.com', 
    'age': '25',
    'city': 'New York'
})

7.3 Creating List Data (Relationships)

# File: redis_data.py, Lines 63-64
r.lpush('user:1:posts', '101', '103')  # Alice wrote posts 101 and 103
r.lpush('user:2:posts', '102')         # Bob wrote post 102

7.4 Creating Set Data (Tags)

# File: redis_data.py, Lines 68-70
r.sadd('tags:101', 'redis', 'database', 'tutorial', 'beginner')
r.sadd('tags:102', 'nosql', 'sql', 'comparison', 'database')
r.sadd('tags:103', 'python', 'redis', 'programming', 'integration')

7.5 Pattern Analysis

# File: redis_schema_analyzer.py, Lines 30-37
def _analyze_key_pattern(self, key):
    parts = key.split(':')
    if len(parts) > 1:
        pattern = ':'.join(['*' if part.isdigit() else part for part in parts])
        self.key_patterns[pattern].append(key)

7.6 SQL Table Creation

# File: sqlbuilder.py, Lines 162-166
create_sql = f"""
CREATE TABLE {table_name} (
    {column_separator.join(columns)}
)
"""
self.cursor.execute(create_sql)

7.7 Data Insertion

# File: redis2mysql.py, Lines 108-112
insert_sql = f"INSERT INTO {table_name} ({columns_str}) VALUES ({placeholders})"
try:
    self.cursor.execute(insert_sql, values)
    print(f"   Migrated {key}")
except Exception as e:
    print(f"   Error migrating {key}: {e}")

8. File Structure

8.1 Complete File Listing

Repository Root: /
├── redis_data.py                    [138 lines] - Redis test data population
├── redis_schema_analyzer.py         [85 lines]  - Pattern detection & analysis
├── redis2mysql.py                   [323 lines] - Main conversion orchestrator
├── sqlbuilder.py                    [473 lines] - MySQL schema generation
├── checkredis.txt                   [32 lines]  - Redis CLI commands reference
├── redis_conn_tests/
│   ├── redistest.py                 [21 lines]  - Basic Redis connection test
│   └── test_modularity.py           [233 lines] - Modularity testing with different datasets
├── gui_converter.py                             - GUI interface for conversion
├── test_schema_generation.py                    - Schema generation tests
├── test_migration_fix.py                        - Migration testing
└── README.md                                    - Project documentation

8.2 File Dependencies

redis_data.py
    └── Depends on: redis library

redis_schema_analyzer.py
    └── Depends on: redis, json, collections.defaultdict

redis2mysql.py
    ├── Depends on: redis, mysql.connector
    ├── Imports: RedisSchemaAnalyzer (from redis_schema_analyzer.py)
    └── Imports: MySQLSchemaBuilder (from sqlbuilder.py)

sqlbuilder.py
    └── Depends on: re, datetime, collections.defaultdict

gui_converter.py
    └── Imports: RedisToMySQLConverter (from redis2mysql.py)

9. Usage Examples

9.1 Populating Redis with Test Data

# Start Redis server
redis-server

# In another terminal, populate data
python redis_data.py

Output:

Populating Redis with test data...

Cleared existing data

Creating user profiles...

Creating posts...

Creating user post lists...

Creating tag sets...

Creating simple strings and counters...

Creating leaderboard (sorted set)...

Creating user sessions...

Test data population complete!
Total keys created: 18

9.2 Analyzing Redis Schema

python redis_schema_analyzer.py

Output Example:

---------- ANALYZING REDIS DB ----------

Found 18 keys

==================================================
**REDIS SCHEMA ANALYSIS**
==================================================

 KEY PATTERNS:
  user:* -> 3 keys
    Examples: ['user:1', 'user:2', 'user:3']
  post:* -> 3 keys
    Examples: ['post:101', 'post:102', 'post:103']
  user:*:posts -> 2 keys
    Examples: ['user:1:posts', 'user:2:posts']
  tags:* -> 3 keys
    Examples: ['tags:101', 'tags:102', 'tags:103']

 DATA TYPES:
  hash: 6 keys
  list: 2 keys
  set: 3 keys
  string: 5 keys
  zset: 2 keys

 SAMPLE DATA:
  user:1: {'name': 'Alice Johnson', 'email': 'alice@example.com', 'age': '25', 'city': 'New York'}
  post:101: {'title': 'Getting Started with Redis', 'author_id': '1', 'content': 'Redis is a powerful...'}

9.3 Running Full Migration

python redis2mysql.py

Output Example:

Starting Redis to MySQL conversion...
Connected to MySQL!
Created new database: redis_converted
Using database: redis_converted

---------- ANALYZING REDIS DB ----------
Found 18 keys
Analysis complete!

============================================================
BUILDING MySQL SCHEMA FROM REDIS PATTERNS
============================================================

PASS 1: Creating entity tables...
    Creating entity table: users
   Created table: users with 6 columns
    Creating entity table: posts
   Created table: posts with 7 columns
    Creating entity table: tags
   Created specialized tags table: tags

PASS 2: Creating relationship tables...
   Created relationship table: user_posts

PASS 3: Creating configuration table...
   Created configuration table

Schema creation complete! Created 4 tables:
   configuration
   posts
   tags
   user_posts
   users

============================================================
MIGRATING DATA FROM REDIS TO MYSQL
============================================================
Found 18 keys to migrate

 Migrating 3 user entities...
   Migrated user:1
   Migrated user:2
   Migrated user:3

 Migrating 3 post entities...
   Migrated post:101
   Migrated post:102
   Migrated post:103

 Migrating 2 posts relationships...
   Migrated user:1:posts
   Migrated user:2:posts

Migrating tags sets to tags table...
   Migrated 12 tags to tags table.

Migrating 3 configuration entries...
   Migrated site:visitor_count
   Migrated site:maintenance_mode
   Migrated config:max_posts_per_user

Migration complete! Migrated 18 keys
Conversion complete!
Connections closed

9.4 Verifying Migration with MySQL

-- Check created tables
SHOW TABLES;

-- View users table structure
DESCRIBE users;

-- View migrated user data
SELECT * FROM users;

-- View relationship data
SELECT * FROM user_posts;

-- View tags
SELECT * FROM tags;

-- View configuration
SELECT * FROM configuration;

9.5 Using Command Line Arguments

# Test connections only
python redis2mysql.py --test

# Show Redis data before migration
python redis2mysql.py --show-data

# Show migration results after completion
python redis2mysql.py --show-results

# Verbose mode
python redis2mysql.py --verbose

# Combine flags
python redis2mysql.py --show-data --show-results --verbose

9.6 Testing with Different Datasets

# Test modularity with e-commerce and blog datasets
python redis_conn_tests/test_modularity.py

This demonstrates the universal modularity of the system by migrating completely different Redis structures without code changes.


10. Redis CLI Verification Commands

Reference file: checkredis.txt

Basic Commands

# See all keys
KEYS *

# See specific pattern keys
KEYS user:*
KEYS post:*
KEYS user:*:posts

# Get total key count
DBSIZE

# Check if key exists
EXISTS user:1

Hash Commands

# Get all fields and values
HGETALL user:1

# Get specific fields
HMGET user:1 name email age

# Get single field
HGET user:1 name

List Commands

# Get all elements
LRANGE user:1:posts 0 -1

# Get first element
LINDEX user:1:posts 0

# Get list length
LLEN user:1:posts

Set Commands

# Get all members
SMEMBERS tags:101

# Get set size
SCARD tags:101

String Commands

# Get value
GET site:visitor_count

11. Advanced Features

11.1 Special Table Handling

Tags Table:

  • Specialized structure for many-to-many relationships
  • Unique constraint on (post_id, tag) combination
  • Indexes for efficient querying

Configuration Table:

  • Stores miscellaneous key-value pairs
  • Includes data_type column for type preservation
  • Generic structure for non-entity data

11.2 Error Handling

  • Connection Failures: Graceful error messages
  • Migration Errors: Per-key error logging, continues with remaining keys
  • Type Inference Failures: Falls back to VARCHAR(255)
  • Reserved Words: Automatic escaping with backticks

11.3 Performance Optimizations

  • Batch Operations: Commits all changes in single transaction
  • Index Creation: Adds indexes for foreign key columns
  • Prepared Statements: Uses parameterized queries to prevent SQL injection

11.4 Modularity Features

The system is designed to work with ANY Redis key pattern:

  1. No Hardcoded Schemas: All tables generated dynamically
  2. Pattern Agnostic: Works with any colon-separated naming
  3. Type Flexible: Infers types from actual data
  4. Relationship Smart: Detects relationships from key structure

Proven with test datasets:

  • E-commerce (customers, products, orders)
  • Blog platform (authors, articles, categories)
  • Original dataset (users, posts, tags)

12. Configuration

12.1 Redis Configuration

# File: redis_schema_analyzer.py, Line 7
redis.Redis(host='localhost', port=6379, db=0)

Customization:

  • host: Redis server hostname
  • port: Redis server port (default 6379)
  • db: Database number (0-15)

12.2 MySQL Configuration

# File: redis2mysql.py, Lines 289-294
mysql_config = {
    'host': 'localhost',
    'user': 'root',
    'password': 'root', 
    'database': 'redis_converted'
}

Customization:

  • host: MySQL server hostname
  • user: MySQL username
  • password: MySQL password
  • database: Target database name

13. Testing

13.1 Unit Testing Files

  1. test_schema_generation.py: Tests schema building logic
  2. test_migration_fix.py: Tests data migration
  3. redis_conn_tests/redistest.py: Basic connectivity test
  4. redis_conn_tests/test_modularity.py: Modularity verification

13.2 Manual Testing Steps

  1. Populate Redis: Run redis_data.py
  2. Verify Data: Use Redis CLI or checkredis.txt commands
  3. Run Analysis: Execute redis_schema_analyzer.py
  4. Perform Migration: Execute redis2mysql.py
  5. Verify MySQL: Query tables in MySQL

14. Troubleshooting

Common Issues

  1. Redis Connection Failed

    • Ensure Redis server is running: redis-server
    • Check host/port configuration
  2. MySQL Connection Failed

    • Verify MySQL server is running
    • Check credentials in config
    • Ensure user has database creation privileges
  3. Schema Creation Errors

    • Check for reserved word conflicts
    • Verify sample data availability
    • Review error logs for specific issues
  4. Data Migration Errors

    • Ensure schema created successfully
    • Check data type compatibility
    • Review per-key error messages

15. Summary

This Redis to MySQL migration system provides:

✅ Automatic pattern detection from Redis keys
✅ Intelligent schema generation with type inference
✅ Relationship preservation through junction tables
✅ Universal modularity works with any Redis structure
✅ Error resilience with graceful error handling
✅ Data integrity through proper constraints and indexes

The implementation demonstrates advanced database concepts including:

  • NoSQL to SQL conversion
  • Dynamic schema generation
  • Pattern recognition algorithms
  • Type inference heuristics
  • Relationship mapping
  • Data migration strategies

All code is modular, well-documented, and follows best practices for database operations.


End of Documentation

Generated: October 2024
Version: 1.0
Project: NoSQL2SQL - Redis to MySQL Migration