Omics-OS Docs
Advanced

43. S3 Backend Implementation GuidePremium

The S3 Backend enables cloud storage for Lobster AI data using AWS S3, providing scalable, durable, and collaborative storage for multi-omics datasets. T...

PremiumThis is premium content. Upgrade for full access

Overview

The S3 Backend enables cloud storage for Lobster AI data using AWS S3, providing scalable, durable, and collaborative storage for multi-omics datasets. This guide covers setup, configuration, usage patterns, and best practices for integrating S3 with Lobster's DataManagerV2 system.

Why Use S3 Backend?

Use CaseBenefit
Team CollaborationShare datasets across research teams without manual file transfers
ScalabilityStore TB-scale datasets without local disk limitations
Cost EfficiencyPay only for storage used; leverage S3 lifecycle policies
Durability99.999999999% (11 nines) durability with automatic replication
Version ControlS3 versioning provides automatic backup and rollback
Multi-RegionAccess data from anywhere with global availability

Architecture Overview

Current Status (v0.2+)

Implementation Status: The S3 backend is currently in planning/partial implementation phase. The architecture is designed to support S3, but full functionality is not yet available.

What's Available Now

S3-Ready Path Handling

from lobster.core.backends.h5ad_backend import H5ADBackend

backend = H5ADBackend(base_path="./data")

# Path parsing already supports S3 URIs
backend._is_s3_path("s3://my-bucket/data/file.h5ad")  # Returns True
backend._is_s3_path("./local/path.h5ad")  # Returns False

S3 Configuration Storage

# Configure S3 settings (stored but not yet used)
backend.configure_s3(
    bucket="my-lobster-data",
    region="us-east-1",
    access_key="your-access-key",
    secret_key="your-secret-key"
)

print(backend.s3_config)
# {'bucket': 'my-lobster-data', 'region': 'us-east-1', ...}

What's Coming Soon

🚧 Full S3 Implementation (Roadmap for future versions)

  • S3 read/write operations via boto3
  • Multipart upload for large files (>100MB)
  • S3 caching with local fallback
  • S3 Select for efficient querying
  • Cross-region replication support
  • Integration with AWS IAM roles

Estimated Timeline: Q2 2025

AWS S3 Setup

Prerequisites

Before using S3 backend, you need:

  1. AWS Account - Create at https://aws.amazon.com
  2. AWS CLI - Install: pip install awscli
  3. boto3 Library - Install: pip install boto3
  4. S3 Bucket - Created in your AWS account
  5. IAM Credentials - Access key and secret key with S3 permissions

Step 1: Create S3 Bucket

Via AWS Console:

  1. Go to https://console.aws.amazon.com/s3/
  2. Click "Create bucket"
  3. Choose a unique bucket name: my-lobster-data-<random-id>
  4. Select region: us-east-1 (or closest to you)
  5. Block Public Access: Keep default (block all)
  6. Versioning: Enable (recommended for data safety)
  7. Encryption: Enable SSE-S3 (server-side encryption)
  8. Click "Create bucket"

Via AWS CLI:

# Create bucket with encryption and versioning
aws s3api create-bucket \
  --bucket my-lobster-data-20250116 \
  --region us-east-1 \
  --create-bucket-configuration LocationConstraint=us-east-1

# Enable versioning
aws s3api put-bucket-versioning \
  --bucket my-lobster-data-20250116 \
  --versioning-configuration Status=Enabled

# Enable encryption
aws s3api put-bucket-encryption \
  --bucket my-lobster-data-20250116 \
  --server-side-encryption-configuration \
  '{"Rules": [{"ApplyServerSideEncryptionByDefault": {"SSEAlgorithm": "AES256"}}]}'

Step 2: Create IAM User and Policy

Create IAM Policy for Lobster:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket",
        "s3:GetBucketLocation"
      ],
      "Resource": "arn:aws:s3:::my-lobster-data-*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject",
        "s3:GetObjectVersion",
        "s3:ListMultipartUploadParts",
        "s3:AbortMultipartUpload"
      ],
      "Resource": "arn:aws:s3:::my-lobster-data-*/*"
    }
  ]
}

Create IAM User:

# Create user
aws iam create-user --user-name lobster-s3-user

# Attach policy
aws iam put-user-policy \
  --user-name lobster-s3-user \
  --policy-name LobsterS3Access \
  --policy-document file://lobster-s3-policy.json

# Create access keys
aws iam create-access-key --user-name lobster-s3-user

Output:

{
  "AccessKey": {
    "UserName": "lobster-s3-user",
    "AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
    "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  }
}

⚠️ Security Warning: Save these credentials securely! They won't be shown again.

Step 3: Configure AWS Credentials

Method 1: Environment Variables (Recommended for development)

export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_DEFAULT_REGION="us-east-1"

Method 2: AWS Credentials File (Recommended for production)

# Configure AWS CLI (creates ~/.aws/credentials)
aws configure

# Manually edit ~/.aws/credentials
cat > ~/.aws/credentials <<EOF
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
EOF

# Set region in ~/.aws/config
cat > ~/.aws/config <<EOF
[default]
region = us-east-1
output = json
EOF

Method 3: IAM Roles (Recommended for EC2/ECS)

# On AWS EC2 instances, use IAM roles (no credentials needed)
# Attach IAM role to EC2 instance with LobsterS3Access policy
# boto3 automatically discovers credentials from instance metadata

Configuration

Basic Configuration

from lobster.core.backends.h5ad_backend import H5ADBackend
from lobster.core.data_manager_v2 import DataManagerV2

# Initialize S3-ready backend
s3_backend = H5ADBackend(
    base_path=None,  # No local base path for S3
    compression="gzip",
    compression_opts=6
)

# Configure S3 settings
s3_backend.configure_s3(
    bucket="my-lobster-data-20250116",
    region="us-east-1",
    access_key="AKIAIOSFODNN7EXAMPLE",  # Or use None to load from ~/.aws/credentials
    secret_key="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"  # Or None
)

# Use with DataManagerV2
data_manager = DataManagerV2(
    workspace_path="./workspace",
    default_backend="h5ad"
)

# Register S3 backend
data_manager.register_backend("s3", s3_backend)

Hybrid Configuration (Local + S3)

from pathlib import Path

# Local backend for fast access
local_backend = H5ADBackend(base_path="./data")

# S3 backend for cloud storage
s3_backend = H5ADBackend(base_path=None)
s3_backend.configure_s3(
    bucket="my-lobster-data-20250116",
    region="us-east-1"
)

# Register both backends
dm = DataManagerV2(workspace_path="./workspace")
dm.register_backend("local", local_backend)
dm.register_backend("s3", s3_backend)

# Use local for active analysis
dm.default_backend = "local"

# Switch to S3 for sharing
dm.default_backend = "s3"

Usage Patterns

Pattern 1: Direct S3 Paths (Planned Feature)

Future API (future versions):

# Load from S3 (when fully implemented)
adata = data_manager.load_modality(
    "s3://my-lobster-data/experiments/gse180759.h5ad",
    modality_name="rna_seq"
)

# Save to S3
data_manager.save_modality(
    modality_name="rna_seq_processed",
    path="s3://my-lobster-data/experiments/gse180759_processed.h5ad"
)

Pattern 2: Workspace with S3 Backend

Current Workaround (v0.2):

import boto3
from pathlib import Path

# Initialize boto3 S3 client
s3_client = boto3.client(
    's3',
    aws_access_key_id='AKIAIOSFODNN7EXAMPLE',
    aws_secret_access_key='wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',
    region_name='us-east-1'
)

# Save locally first
local_path = Path("./workspace/data/gse180759.h5ad")
data_manager.save_modality("rna_seq", local_path)

# Upload to S3
s3_client.upload_file(
    Filename=str(local_path),
    Bucket='my-lobster-data-20250116',
    Key='experiments/gse180759.h5ad'
)

print(f"✅ Uploaded to S3: s3://my-lobster-data-20250116/experiments/gse180759.h5ad")

# Download from S3 for loading
s3_download_path = Path("./workspace/cache/gse180759.h5ad")
s3_client.download_file(
    Bucket='my-lobster-data-20250116',
    Key='experiments/gse180759.h5ad',
    Filename=str(s3_download_path)
)

# Load into DataManager
adata = data_manager.load_modality(s3_download_path, modality_name="rna_seq_s3")

Pattern 3: Sharing Datasets Between Team Members

Scenario: Researcher A processes data, uploads to S3. Researcher B downloads and continues analysis.

Researcher A (Upload):

# Process data
from lobster.tools.preprocessing_service import PreprocessingService

prep = PreprocessingService()
adata_processed, stats, ir = prep.normalize_and_scale(adata)

# Save locally
data_manager.save_modality("gse180759_processed", "./data/gse180759_proc.h5ad")

# Upload to S3 (current workaround)
s3_client.upload_file(
    "./workspace/data/gse180759_proc.h5ad",
    "my-lobster-data-20250116",
    "team-shared/gse180759_processed.h5ad"
)

# Share S3 URI with team
print("Share this URI: s3://my-lobster-data-20250116/team-shared/gse180759_processed.h5ad")

Researcher B (Download):

# Download from shared S3
s3_client.download_file(
    Bucket='my-lobster-data-20250116',
    Key='team-shared/gse180759_processed.h5ad',
    Filename='./workspace/data/shared_gse180759.h5ad'
)

# Load and continue analysis
adata = data_manager.load_modality("./workspace/data/shared_gse180759.h5ad", "shared_rna")

Pattern 4: S3 Versioning for Reproducibility

import boto3
from datetime import datetime

s3_client = boto3.client('s3')

# Save with version tag
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
s3_key = f"experiments/gse180759_{timestamp}.h5ad"

data_manager.save_modality("rna_seq", "./workspace/data/temp.h5ad")
s3_client.upload_file(
    "./workspace/data/temp.h5ad",
    "my-lobster-data-20250116",
    s3_key
)

# List all versions
response = s3_client.list_object_versions(
    Bucket='my-lobster-data-20250116',
    Prefix='experiments/gse180759_'
)

for version in response.get('Versions', []):
    print(f"{version['Key']} - {version['LastModified']} (Version: {version['VersionId']})")

# Download specific version
s3_client.download_file(
    Bucket='my-lobster-data-20250116',
    Key='experiments/gse180759_20250116_143000.h5ad',
    Filename='./workspace/data/gse180759_v1.h5ad'
)

Performance Considerations

Benchmarks: Local vs S3 Storage

Test Setup:

  • Dataset: GSE180759 (36 samples, 30K genes, 180MB H5AD file)
  • Network: 100 Mbps connection
  • S3 Region: us-east-1
  • Local: SSD storage
OperationLocal (SSD)S3 (100 Mbps)Difference
Write2.3 seconds14.5 seconds6.3x slower
Read1.8 seconds12.1 seconds6.7x slower
List0.05 seconds0.3 seconds6x slower
Metadata0.01 seconds0.2 seconds20x slower

Benchmark Metadata:

  • Date: 2025-01-16
  • Lobster Version: v0.2.0
  • boto3 Version: 1.34.0
  • Network: AWS Direct Connect (simulated 100 Mbps)

Optimization Strategies

1. Compression for Faster Uploads

# Use maximum compression for S3 uploads (slower write, smaller file)
backend = H5ADBackend(
    base_path=None,
    compression="gzip",
    compression_opts=9  # Maximum compression (1-9, default 6)
)

# Trade-off: 20% smaller file, 15% longer compression time
# Net benefit: 15% faster upload on slow networks (<10 Mbps)

2. Local Caching Strategy

from pathlib import Path
import hashlib

def load_with_cache(s3_key: str, bucket: str) -> anndata.AnnData:
    """Load from S3 with local cache to avoid repeated downloads."""

    # Generate cache key
    cache_key = hashlib.md5(f"{bucket}/{s3_key}".encode()).hexdigest()
    cache_path = Path(f"./workspace/cache/{cache_key}.h5ad")

    if cache_path.exists():
        print(f"✅ Cache hit: {cache_path}")
        return data_manager.load_modality(cache_path, f"cached_{cache_key}")

    # Cache miss - download from S3
    print(f"❌ Cache miss - downloading from S3")
    s3_client.download_file(bucket, s3_key, str(cache_path))
    return data_manager.load_modality(cache_path, f"cached_{cache_key}")

3. Multipart Upload for Large Files (>100MB)

import boto3
from boto3.s3.transfer import TransferConfig

# Configure multipart upload
config = TransferConfig(
    multipart_threshold=100 * 1024 * 1024,  # 100 MB
    max_concurrency=10,
    multipart_chunksize=10 * 1024 * 1024,  # 10 MB chunks
    use_threads=True
)

# Upload with progress callback
def progress_callback(bytes_transferred):
    print(f"Uploaded {bytes_transferred / (1024**2):.2f} MB")

s3_client.upload_file(
    "./workspace/data/large_dataset.h5ad",
    "my-lobster-data-20250116",
    "experiments/large_dataset.h5ad",
    Config=config,
    Callback=progress_callback
)

4. S3 Select for Efficient Querying (Future Feature)

# Future API: Query S3 without downloading entire file
result = s3_backend.query(
    "s3://my-lobster-data/gse180759.h5ad",
    query="SELECT obs WHERE obs.cell_type = 'T cell'",
    output_format="parquet"
)

Cost Optimization

S3 Storage Classes

Storage ClassUse CaseCost (GB/month)Retrieval Time
S3 StandardActive datasets$0.023Milliseconds
S3 Intelligent-TieringUnpredictable access$0.021 + monitoringMilliseconds
S3 Infrequent AccessArchive (>30 days old)$0.0125Milliseconds
S3 Glacier InstantLong-term archive$0.004Milliseconds
S3 Glacier FlexibleRare access$0.0036Minutes-hours

Pricing as of January 2025 (us-east-1)

Lifecycle Policies

Automatic Tiering for Cost Savings:

{
  "Rules": [
    {
      "Id": "Archive old experiments",
      "Status": "Enabled",
      "Filter": {
        "Prefix": "experiments/"
      },
      "Transitions": [
        {
          "Days": 30,
          "StorageClass": "STANDARD_IA"
        },
        {
          "Days": 90,
          "StorageClass": "GLACIER_IR"
        }
      ]
    },
    {
      "Id": "Delete temporary data",
      "Status": "Enabled",
      "Filter": {
        "Prefix": "temp/"
      },
      "Expiration": {
        "Days": 7
      }
    }
  ]
}

Apply via AWS CLI:

aws s3api put-bucket-lifecycle-configuration \
  --bucket my-lobster-data-20250116 \
  --lifecycle-configuration file://lifecycle.json

Cost Estimation Example

Scenario: 100 single-cell datasets, 200MB each, analyzed monthly

ComponentCalculationCost
Storage100 datasets × 200 MB × $0.023/GB$0.46/month
PUT Requests100 uploads × $0.005/1000$0.0005
GET Requests300 downloads × $0.0004/1000$0.00012
Data Transfer60 GB out × $0.09/GB$5.40/month
Total$5.86/month

Cost Optimization Tips:

  • Use S3 Intelligent-Tiering: Save 40% on infrequently accessed data
  • Enable compression: Reduce storage by 30-50%
  • Use S3 Transfer Acceleration for faster uploads (adds cost but saves time)
  • Keep frequently accessed data local, archive to S3

Security

Encryption at Rest

Server-Side Encryption (SSE-S3):

# Automatic encryption when uploading
s3_client.upload_file(
    "./data/sensitive.h5ad",
    "my-lobster-data-20250116",
    "experiments/sensitive.h5ad",
    ExtraArgs={'ServerSideEncryption': 'AES256'}
)

Server-Side Encryption with KMS (SSE-KMS):

# Use AWS KMS for key management
s3_client.upload_file(
    "./data/sensitive.h5ad",
    "my-lobster-data-20250116",
    "experiments/sensitive.h5ad",
    ExtraArgs={
        'ServerSideEncryption': 'aws:kms',
        'SSEKMSKeyId': 'arn:aws:kms:us-east-1:123456789012:key/abcd-1234'
    }
)

Encryption in Transit

All S3 API calls use HTTPS by default:

# Force HTTPS (default behavior)
s3_client = boto3.client(
    's3',
    region_name='us-east-1',
    config=boto3.session.Config(signature_version='s3v4')
)

Bucket Policies

Restrict Access to Specific IP Range:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::my-lobster-data-20250116",
        "arn:aws:s3:::my-lobster-data-20250116/*"
      ],
      "Condition": {
        "NotIpAddress": {
          "aws:SourceIp": [
            "203.0.113.0/24",
            "198.51.100.0/24"
          ]
        }
      }
    }
  ]
}

Access Logging

Enable S3 Access Logs:

# Create logging bucket
aws s3api create-bucket \
  --bucket my-lobster-logs \
  --region us-east-1

# Enable logging
aws s3api put-bucket-logging \
  --bucket my-lobster-data-20250116 \
  --bucket-logging-status \
  '{"LoggingEnabled": {"TargetBucket": "my-lobster-logs", "TargetPrefix": "access-logs/"}}'

Analyze Access Patterns:

# Download logs
aws s3 sync s3://my-lobster-logs/access-logs/ ./logs/

# Analyze with Python
import pandas as pd

logs = pd.read_csv("./logs/access-logs.csv", sep=" ", header=None)
print(f"Total requests: {len(logs)}")
print(f"GET requests: {logs[5].str.contains('GET').sum()}")
print(f"PUT requests: {logs[5].str.contains('PUT').sum()}")

Troubleshooting

Issue 1: Credentials Not Found

Error:

botocore.exceptions.NoCredentialsError: Unable to locate credentials

Solution:

# Check AWS credentials
aws sts get-caller-identity

# If no credentials, configure
aws configure

Issue 2: Permission Denied

Error:

botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the GetObject operation

Solution:

# Check IAM policy
aws iam get-user-policy \
  --user-name lobster-s3-user \
  --policy-name LobsterS3Access

# Ensure policy includes s3:GetObject permission

Issue 3: Bucket Not Found

Error:

botocore.exceptions.ClientError: An error occurred (NoSuchBucket) when calling the PutObject operation

Solution:

# Verify bucket exists
aws s3 ls s3://my-lobster-data-20250116

# Check region
aws s3api get-bucket-location --bucket my-lobster-data-20250116

Issue 4: Slow Upload/Download

Symptoms:

  • Upload speed <1 MB/s
  • Timeouts on large files

Solution:

# Enable S3 Transfer Acceleration
s3_client = boto3.client(
    's3',
    region_name='us-east-1',
    config=boto3.session.Config(
        s3={'use_accelerate_endpoint': True}
    )
)

# Or use multipart upload (see Performance section)

Issue 5: Network Timeouts

Error:

botocore.exceptions.ReadTimeoutError: Read timeout on endpoint URL

Solution:

from botocore.config import Config

# Increase timeout
config = Config(
    connect_timeout=10,
    read_timeout=60,
    retries={'max_attempts': 5}
)

s3_client = boto3.client('s3', config=config)

Migration Guide

Moving from Local to S3

Step 1: Inventory Local Datasets

from pathlib import Path

local_data_dir = Path("./workspace/data")
datasets = list(local_data_dir.glob("*.h5ad"))

print(f"Found {len(datasets)} local datasets:")
for ds in datasets:
    size_mb = ds.stat().st_size / (1024**2)
    print(f"  {ds.name}: {size_mb:.2f} MB")

Step 2: Upload to S3 with Progress

import boto3
from tqdm import tqdm

s3_client = boto3.client('s3')
bucket = 'my-lobster-data-20250116'

for dataset_path in tqdm(datasets, desc="Uploading to S3"):
    s3_key = f"migrated/{dataset_path.name}"

    s3_client.upload_file(
        str(dataset_path),
        bucket,
        s3_key
    )

    print(f"✅ Uploaded: s3://{bucket}/{s3_key}")

Step 3: Verify Upload Integrity

import hashlib

def compute_md5(file_path):
    """Compute MD5 checksum of file."""
    hash_md5 = hashlib.md5()
    with open(file_path, "rb") as f:
        for chunk in iter(lambda: f.read(4096), b""):
            hash_md5.update(chunk)
    return hash_md5.hexdigest()

# Verify each upload
for dataset_path in datasets:
    local_md5 = compute_md5(dataset_path)

    s3_key = f"migrated/{dataset_path.name}"
    response = s3_client.head_object(Bucket=bucket, Key=s3_key)
    s3_etag = response['ETag'].strip('"')

    if local_md5 == s3_etag:
        print(f"✅ Verified: {dataset_path.name}")
    else:
        print(f"❌ Checksum mismatch: {dataset_path.name}")

Step 4: Update Workspace Configuration

# Update DataManager to use S3 backend
data_manager.default_backend = "s3"

# Optionally delete local copies to save disk space
# for dataset_path in datasets:
#     dataset_path.unlink()

Summary

The S3 Backend for Lobster AI provides:

Scalability - Store TB-scale multi-omics datasets without local limits ✅ Durability - 11 nines durability with automatic replication ✅ Collaboration - Share datasets across research teams via S3 URIs ✅ Cost Efficiency - Pay-per-use pricing with lifecycle policies ✅ Security - Encryption at rest/transit, IAM-based access control ✅ Versioning - Automatic backup and rollback capabilities

Current Status (v0.2): S3 backend is in planning phase. Use manual boto3 upload/download as workaround.

Coming Soon (future versions): Native S3 support with seamless s3:// URI handling, multipart uploads, and caching.

Best Practice: Use local storage for active analysis (fast), S3 for archival and sharing (durable, collaborative).

On this page