## 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>
277 lines
9.5 KiB
Markdown
277 lines
9.5 KiB
Markdown
# 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
|