feat: KRW 가격 지원 및 GPU/G8/VPU 인스턴스 추가
## KRW 가격 기능 - pricing 테이블에 hourly_price_krw, monthly_price_krw 컬럼 추가 - 부가세 10% + 영업이익 10% + 환율 적용 (기본 1450원) - 시간당: 1원 단위 반올림 (최소 1원) - 월간: 100원 단위 반올림 (최소 100원) - 환율/부가세/영업이익률 환경변수로 분리 (배포 없이 변경 가능) ## GPU/G8/VPU 인스턴스 지원 - gpu_instances, gpu_pricing 테이블 추가 - g8_instances, g8_pricing 테이블 추가 - vpu_instances, vpu_pricing 테이블 추가 - Linode/Vultr 커넥터에 GPU 동기화 로직 추가 ## 환경변수 추가 - KRW_EXCHANGE_RATE: 환율 (기본 1450) - KRW_VAT_RATE: 부가세율 (기본 1.1) - KRW_MARKUP_RATE: 영업이익률 (기본 1.1) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
276
GPU_IMPLEMENTATION_SUMMARY.md
Normal file
276
GPU_IMPLEMENTATION_SUMMARY.md
Normal file
@@ -0,0 +1,276 @@
|
||||
# GPU Implementation Summary
|
||||
|
||||
## Overview
|
||||
Implemented dedicated GPU instance tables and code infrastructure for Linode GPU instances in the cloud-server project.
|
||||
|
||||
## Files Modified
|
||||
|
||||
### 1. Database Schema (`schema.sql`)
|
||||
Added two new tables with full indexing and triggers:
|
||||
|
||||
#### `gpu_instances` Table
|
||||
- **Purpose**: GPU-specific instance types separate from regular instances
|
||||
- **Key Fields**:
|
||||
- Standard fields: id, provider_id, instance_id, instance_name, vcpu, memory_mb, storage_gb, transfer_tb, network_speed_gbps
|
||||
- GPU-specific: gpu_count (NOT NULL, CHECK > 0), gpu_type (NOT NULL), gpu_memory_gb
|
||||
- Metadata: JSON string for provider-specific additional data
|
||||
- **Indexes**:
|
||||
- `idx_gpu_instances_provider_id` - Provider lookups
|
||||
- `idx_gpu_instances_gpu_type` - GPU type filtering
|
||||
- `idx_gpu_instances_gpu_count` - GPU count filtering
|
||||
- `idx_gpu_instances_provider_type` - Composite index for provider + GPU type queries
|
||||
- **Triggers**: `update_gpu_instances_updated_at` - Auto-update timestamp
|
||||
|
||||
#### `gpu_pricing` Table
|
||||
- **Purpose**: Region-specific pricing for GPU instances
|
||||
- **Key Fields**: gpu_instance_id, region_id, hourly_price, monthly_price, currency, available
|
||||
- **Indexes**:
|
||||
- `idx_gpu_pricing_instance_id` - Instance lookups
|
||||
- `idx_gpu_pricing_region_id` - Region lookups
|
||||
- `idx_gpu_pricing_hourly_price` - Price sorting
|
||||
- `idx_gpu_pricing_monthly_price` - Price sorting
|
||||
- `idx_gpu_pricing_available` - Availability filtering
|
||||
- **Triggers**: `update_gpu_pricing_updated_at` - Auto-update timestamp
|
||||
|
||||
### 2. Type Definitions (`src/types.ts`)
|
||||
Added new types following existing patterns:
|
||||
|
||||
```typescript
|
||||
export interface GpuInstance {
|
||||
id: number;
|
||||
provider_id: number;
|
||||
instance_id: string;
|
||||
instance_name: string;
|
||||
vcpu: number;
|
||||
memory_mb: number;
|
||||
storage_gb: number;
|
||||
transfer_tb: number | null;
|
||||
network_speed_gbps: number | null;
|
||||
gpu_count: number;
|
||||
gpu_type: string;
|
||||
gpu_memory_gb: number | null;
|
||||
metadata: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface GpuPricing {
|
||||
id: number;
|
||||
gpu_instance_id: number;
|
||||
region_id: number;
|
||||
hourly_price: number;
|
||||
monthly_price: number;
|
||||
currency: string;
|
||||
available: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export type GpuInstanceInput = Omit<GpuInstance, 'id' | 'created_at' | 'updated_at'>;
|
||||
export type GpuPricingInput = Omit<GpuPricing, 'id' | 'created_at' | 'updated_at'>;
|
||||
```
|
||||
|
||||
### 3. Repositories
|
||||
|
||||
#### `src/repositories/gpu-instances.ts` - GpuInstancesRepository
|
||||
Extends `BaseRepository<GpuInstance>` with specialized methods:
|
||||
|
||||
**Methods**:
|
||||
- `findByProvider(providerId: number)` - Find all GPU instances for a provider
|
||||
- `findByGpuType(gpuType: string)` - Find instances by GPU type
|
||||
- `findByInstanceId(providerId: number, instanceId: string)` - Find specific instance
|
||||
- `upsertMany(providerId: number, instances: GpuInstanceInput[])` - Bulk upsert with conflict resolution
|
||||
- `search(criteria)` - Advanced search with filters for vCPU, memory, GPU count, GPU type, GPU memory
|
||||
- `getAvailableGpuTypes()` - Get distinct GPU types in database
|
||||
|
||||
**Features**:
|
||||
- Follows BaseRepository pattern
|
||||
- Uses `createLogger('[GpuInstancesRepository]')`
|
||||
- Batch operations for efficiency
|
||||
- Comprehensive error handling with RepositoryError
|
||||
- Proper parameter binding for SQL injection prevention
|
||||
|
||||
#### `src/repositories/gpu-pricing.ts` - GpuPricingRepository
|
||||
Extends `BaseRepository<GpuPricing>` for pricing operations:
|
||||
|
||||
**Methods**:
|
||||
- `findByGpuInstance(gpuInstanceId: number)` - Get all pricing for GPU instance
|
||||
- `findByRegion(regionId: number)` - Get all GPU pricing in region
|
||||
- `findByGpuInstanceAndRegion(gpuInstanceId, regionId)` - Get specific pricing record
|
||||
- `upsertMany(pricingData: GpuPricingInput[])` - Bulk upsert pricing data
|
||||
- `searchByPriceRange(minHourly?, maxHourly?, minMonthly?, maxMonthly?)` - Search by price
|
||||
|
||||
**Features**:
|
||||
- Follows BaseRepository pattern
|
||||
- Uses `createLogger('[GpuPricingRepository]')`
|
||||
- Batch operations with conflict resolution
|
||||
- Price range filtering
|
||||
|
||||
### 4. Repository Factory (`src/repositories/index.ts`)
|
||||
Extended RepositoryFactory with GPU repositories:
|
||||
|
||||
```typescript
|
||||
export class RepositoryFactory {
|
||||
private _gpuInstances?: GpuInstancesRepository;
|
||||
private _gpuPricing?: GpuPricingRepository;
|
||||
|
||||
get gpuInstances(): GpuInstancesRepository {
|
||||
return this._gpuInstances ??= new GpuInstancesRepository(this.db);
|
||||
}
|
||||
|
||||
get gpuPricing(): GpuPricingRepository {
|
||||
return this._gpuPricing ??= new GpuPricingRepository(this.db);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Linode Connector (`src/connectors/linode.ts`)
|
||||
Added GPU normalization methods:
|
||||
|
||||
**New Methods**:
|
||||
- `normalizeGpuInstance(raw: LinodeInstanceType, providerId: number): GpuInstanceInput`
|
||||
- Normalizes Linode GPU instance data for database storage
|
||||
- Extracts GPU-specific information
|
||||
- Converts units (MB to GB, GB to TB, Mbps to Gbps)
|
||||
- Stores pricing in metadata
|
||||
|
||||
- `extractGpuType(raw: LinodeInstanceType): string` (private)
|
||||
- Intelligent GPU type extraction from instance label
|
||||
- Recognizes: RTX6000, A100, V100, generic RTX
|
||||
- Defaults to "NVIDIA GPU" for unknown types
|
||||
|
||||
**Features**:
|
||||
- Consistent with existing normalization patterns
|
||||
- Proper unit conversions
|
||||
- GPU type detection from instance labels
|
||||
- Metadata preservation
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### Why Separate GPU Tables?
|
||||
1. **Performance**: GPU instances have different query patterns (GPU type, GPU count filters)
|
||||
2. **Schema Clarity**: GPU-specific fields (gpu_memory_gb) don't belong in general instances
|
||||
3. **Extensibility**: Easy to add GPU-specific features without affecting general instances
|
||||
4. **Pricing Separation**: GPU pricing may have different dynamics (spot pricing, regional availability)
|
||||
|
||||
### Why Not Reuse Pricing Table?
|
||||
1. **Foreign Key Clarity**: Separate gpu_instance_id vs instance_type_id prevents confusion
|
||||
2. **Query Optimization**: Dedicated indexes for GPU pricing queries
|
||||
3. **Future Features**: GPU pricing may need GPU-specific fields (spot pricing, tensor core hours)
|
||||
4. **Data Integrity**: Clear separation prevents mixing GPU and regular instance pricing
|
||||
|
||||
### GPU Type Detection Strategy
|
||||
Uses label-based heuristics because:
|
||||
- Linode API doesn't expose specific GPU model in structured fields
|
||||
- Label parsing is reliable for current Linode naming conventions
|
||||
- Extensible: Easy to add new GPU models as they become available
|
||||
- Fallback: Defaults to "NVIDIA GPU" for unknown types
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Usage in Sync Service
|
||||
To integrate with existing sync workflows:
|
||||
|
||||
```typescript
|
||||
// Fetch and separate GPU instances
|
||||
const allInstances = await linodeConnector.fetchInstanceTypes();
|
||||
const gpuInstances = allInstances.filter(inst => inst.gpus > 0);
|
||||
const regularInstances = allInstances.filter(inst => inst.gpus === 0);
|
||||
|
||||
// Normalize and store separately
|
||||
const normalizedGpu = gpuInstances.map(inst =>
|
||||
linodeConnector.normalizeGpuInstance(inst, providerId)
|
||||
);
|
||||
|
||||
await repos.gpuInstances.upsertMany(providerId, normalizedGpu);
|
||||
```
|
||||
|
||||
### Querying GPU Instances
|
||||
```typescript
|
||||
// Find all NVIDIA A100 instances
|
||||
const a100s = await repos.gpuInstances.findByGpuType('NVIDIA A100');
|
||||
|
||||
// Search with filters
|
||||
const results = await repos.gpuInstances.search({
|
||||
providerId: 1,
|
||||
minGpuCount: 2,
|
||||
minMemoryMb: 32768,
|
||||
gpuType: 'NVIDIA RTX6000'
|
||||
});
|
||||
|
||||
// Get available GPU types
|
||||
const gpuTypes = await repos.gpuInstances.getAvailableGpuTypes();
|
||||
```
|
||||
|
||||
## Testing Status
|
||||
|
||||
✅ **TypeScript Compilation**: Passes without errors
|
||||
✅ **Type Safety**: All types properly defined and used
|
||||
✅ **Pattern Consistency**: Follows existing repository and connector patterns
|
||||
⏳ **Unit Tests**: Existing tests still pass (verification in progress)
|
||||
📝 **New Tests Needed**: GPU-specific repository and connector tests
|
||||
|
||||
## Next Steps
|
||||
|
||||
### 1. Sync Service Integration
|
||||
Update `src/services/sync.ts` to:
|
||||
- Separate GPU instances during sync
|
||||
- Store GPU instances in gpu_instances table
|
||||
- Store GPU pricing in gpu_pricing table
|
||||
|
||||
### 2. API Endpoints (Optional)
|
||||
Add GPU-specific endpoints:
|
||||
- `GET /gpu-instances` - Query GPU instances
|
||||
- `GET /gpu-types` - List available GPU types
|
||||
- `GET /gpu-pricing` - Query GPU pricing
|
||||
|
||||
### 3. Testing
|
||||
Create test files:
|
||||
- `src/repositories/gpu-instances.test.ts`
|
||||
- `src/repositories/gpu-pricing.test.ts`
|
||||
- `src/connectors/linode-gpu.test.ts`
|
||||
|
||||
### 4. Database Migration
|
||||
Run schema update on production:
|
||||
```bash
|
||||
npm run db:migrate:remote
|
||||
```
|
||||
|
||||
### 5. Documentation
|
||||
- Update API documentation with GPU endpoints
|
||||
- Add GPU query examples to README
|
||||
- Document GPU type naming conventions
|
||||
|
||||
## Files Created
|
||||
|
||||
1. `/Users/kaffa/cloud-server/src/repositories/gpu-instances.ts` (279 lines)
|
||||
2. `/Users/kaffa/cloud-server/src/repositories/gpu-pricing.ts` (201 lines)
|
||||
|
||||
## Files Modified
|
||||
|
||||
1. `/Users/kaffa/cloud-server/schema.sql` - Added gpu_instances and gpu_pricing tables
|
||||
2. `/Users/kaffa/cloud-server/src/types.ts` - Added GpuInstance and GpuPricing types
|
||||
3. `/Users/kaffa/cloud-server/src/repositories/index.ts` - Added GPU repositories to factory
|
||||
4. `/Users/kaffa/cloud-server/src/connectors/linode.ts` - Added GPU normalization methods
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
# Type check
|
||||
npx tsc --noEmit # ✅ Passes
|
||||
|
||||
# Run tests
|
||||
npm test # ⏳ In progress
|
||||
|
||||
# Check schema
|
||||
cat schema.sql | grep -A 20 "gpu_instances" # ✅ Tables defined
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- GPU memory (gpu_memory_gb) is set to null for Linode as API doesn't provide this
|
||||
- Can be populated manually or from external data sources if needed
|
||||
- Metadata field stores pricing and other provider-specific data as JSON
|
||||
- All repositories follow lazy singleton pattern via RepositoryFactory
|
||||
- Proper error handling with RepositoryError and ErrorCodes
|
||||
- Comprehensive logging with contextual information
|
||||
Reference in New Issue
Block a user