Chuyển đến nội dung chính

レッスン 22: FHIR のパフォーマンス、スケーラビリティ、およびモニタリング

FHIR サーバーのパフォーマンス、キャッシュ戦略、データベースのインデックス作成、水平スケーリング、ロード バランシング、静的コンテンツの CDN、Prometheus/Grafana による監視、ヘルス チェック、ヘルス SLA を最適化します。

🏗️ アーキテクチャ — レッスン 22 レッスン 22: FHIR のパフォーマンス、スケーラビリティ、および モニタリング

HL7 FHIR - 基本から高度な医療データ標準

パート 7: 生産、規模、将来

xdev.asia

1. 医療におけるパフォーマンスの課題

医療用 FHIR サーバー システムは、稼働時間 ≥ 99.9%、応答時間 < 500ms、数百万のリソースなどの高い要件を満たす必要があります。

因子チャレンジターゲット
応答時間大規模なデータセットに対する複雑な検索< 500ms (P95)
スループット多くの病院が同時に問い合わせる≥ 1000 リクエスト/秒
データ量何百万もの患者、何十億もの観察結核スケール
可用性24時間365日の救急対応≥ 99.9%
一貫性医療データは正確でなければなりません強い一貫性

2. データベースの最適化

HAPI FHIR の PostgreSQL インデックス作成

-- Index cho search by identifier (CCCD, BHYT)
CREATE INDEX idx_spidx_token_hash 
ON hfj_spidx_token (hash_identity, sp_value_normalized)
WHERE sp_missing = false;

-- Index cho search by name
CREATE INDEX idx_spidx_string_normalized 
ON hfj_spidx_string (hash_identity, sp_value_normalized)
WHERE sp_missing = false;

-- Index cho search by date (Observation.date)
CREATE INDEX idx_spidx_date_range 
ON hfj_spidx_date (hash_identity, sp_value_low, sp_value_high)
WHERE sp_missing = false;

-- Index cho _lastUpdated sorting
CREATE INDEX idx_res_updated 
ON hfj_resource (res_updated DESC, res_id)
WHERE res_deleted_at IS NULL;

-- Partitioning cho Observation (table lớn nhất)
CREATE TABLE hfj_res_observation PARTITION OF hfj_resource
FOR VALUES IN ('Observation')
PARTITION BY RANGE (res_updated);

CREATE TABLE hfj_res_observation_2025_q1 
PARTITION OF hfj_res_observation
FOR VALUES FROM ('2025-01-01') TO ('2025-04-01');

クエリの最適化

# application.yml - HAPI FHIR tuning
hapi:
  fhir:
    # Search caching
    reuse_cached_search_results_millis: 60000
    
    # Pagination
    default_page_size: 20
    max_page_size: 200
    
    # Prefetch
    search_prefetch_thresholds:
      - 13  # offset 0
      - 50  # offset 1
      - 200 # offset 2+
    
    # Inline resource storage (tránh JOIN)
    inline_resource_storage_below_size: 4096

spring:
  datasource:
    hikari:
      maximum-pool-size: 50
      minimum-idle: 10
      connection-timeout: 5000
      idle-timeout: 300000

3. キャッシュ戦略

┌────────┐   ┌───────┐   ┌──────────┐   ┌──────┐
│ Client │──▶│ CDN/  │──▶│  Redis   │──▶│ FHIR │
│        │   │ Nginx │   │  Cache   │   │Server│
└────────┘   └───────┘   └──────────┘   └──────┘
// Redis cache cho CapabilityStatement và ValueSet
@Configuration
public class FhirCacheConfig {
    
    @Bean
    public CacheManager cacheManager(
            RedisConnectionFactory redisFactory) {
        RedisCacheConfiguration config = 
            RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))
                .serializeValuesWith(
                    SerializationPair.fromSerializer(
                        new GenericJackson2JsonRedisSerializer()));
        
        return RedisCacheManager.builder(redisFactory)
            .cacheDefaults(config)
            .withCacheConfiguration("metadata",
                config.entryTtl(Duration.ofHours(1)))
            .withCacheConfiguration("valueset",
                config.entryTtl(Duration.ofHours(24)))
            .build();
    }
}

// Interceptor: cache CapabilityStatement
@Component
public class MetadataCacheInterceptor {
    
    @Autowired
    private CacheManager cacheManager;
    
    @Hook(Pointcut.SERVER_CAPABILITY_STATEMENT_GENERATED)
    public void cacheCapabilityStatement(
            IBaseConformance cs) {
        cacheManager.getCache("metadata")
            .put("capability", cs);
    }
}

HTTP キャッシュヘッダー

# nginx.conf
location /fhir/metadata {
    proxy_pass http://fhir-server:8080;
    proxy_cache fhir_cache;
    proxy_cache_valid 200 1h;
    add_header X-Cache-Status $upstream_cache_status;
}

location /fhir/ValueSet {
    proxy_pass http://fhir-server:8080;
    proxy_cache fhir_cache;
    proxy_cache_valid 200 24h;
    proxy_cache_key "$request_uri";
}

# Dynamic resources — no cache
location /fhir/Patient {
    proxy_pass http://fhir-server:8080;
    add_header Cache-Control "no-store";
}

4. 水平方向のスケーリング

# docker-compose-ha.yml
services:
  nginx:
    image: nginx:alpine
    ports:
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - fhir-1
      - fhir-2
      - fhir-3

  fhir-1:
    image: hapiproject/hapi:latest
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://pgpool:5432/hapi_fhir
      - JAVA_OPTS=-Xmx2g
  
  fhir-2:
    image: hapiproject/hapi:latest
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://pgpool:5432/hapi_fhir
      - JAVA_OPTS=-Xmx2g
  
  fhir-3:
    image: hapiproject/hapi:latest
    environment:
      - SPRING_DATASOURCE_URL=jdbc:postgresql://pgpool:5432/hapi_fhir
      - JAVA_OPTS=-Xmx2g

  pgpool:
    image: bitnami/pgpool:latest
    environment:
      - PGPOOL_BACKEND_NODES=0:pg-primary:5432,1:pg-replica:5432
      - PGPOOL_ENABLE_LOAD_BALANCING=yes
      - PGPOOL_SR_CHECK_USER=repmgr

  pg-primary:
    image: bitnami/postgresql-repmgr:16
    environment:
      - POSTGRESQL_DATABASE=hapi_fhir
      - REPMGR_NODE_TYPE=primary

  pg-replica:
    image: bitnami/postgresql-repmgr:16
    environment:
      - REPMGR_NODE_TYPE=standby
      - REPMGR_PRIMARY_HOST=pg-primary

  redis:
    image: redis:7-alpine
    command: redis-server --maxmemory 1gb --maxmemory-policy allkeys-lru

5. Prometheus + Grafana によるモニタリング

メトリクスの公開

# application.yml
management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus
  metrics:
    export:
      prometheus:
        enabled: true
    tags:
      application: fhir-server

プロメテウス構成

# prometheus.yml
scrape_configs:
  - job_name: 'fhir-server'
    metrics_path: '/actuator/prometheus'
    scrape_interval: 15s
    static_configs:
      - targets:
          - 'fhir-1:8080'
          - 'fhir-2:8080'
          - 'fhir-3:8080'

主要な指標は追跡が必要です

メトリック説明アラートしきい値
http_server_requests_秒_p95P95 応答時間> 500ミリ秒
http_server_requests_totalリクエストレート異常検知
jvm_memory_used_bytesJVM ヒープ使用量> 80%
hikaricp_connections_activeアクティブな DB 接続> 80% のプール
fhir_search_duration_秒検索クエリ時間> 1秒
fhir_resource_count総リソース成長率
pg_stat_activity_countPostgreSQL 接続> 最大 90%
ディスク使用率_パーセントディスク容量> 85%

ヘルスチェックエンドポイント

@Component
public class FhirHealthIndicator implements HealthIndicator {
    
    @Autowired
    private IGenericClient fhirClient;
    
    @Override
    public Health health() {
        try {
            CapabilityStatement cs = fhirClient
                .capabilities()
                .ofType(CapabilityStatement.class)
                .execute();
            
            return Health.up()
                .withDetail("fhirVersion", cs.getFhirVersion().toCode())
                .withDetail("resourceCount", 
                    cs.getRest().get(0).getResource().size())
                .build();
        } catch (Exception e) {
            return Health.down()
                .withException(e)
                .build();
        }
    }
}

6. アラートルール

# alerting-rules.yml
groups:
  - name: fhir-server
    rules:
      - alert: FHIRHighResponseTime
        expr: |
          histogram_quantile(0.95,
            rate(http_server_requests_seconds_bucket{uri=~"/fhir/.*"}[5m])
          ) > 0.5
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "FHIR Server P95 response time > 500ms"

      - alert: FHIRServerDown
        expr: up{job="fhir-server"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "FHIR Server instance down"

      - alert: FHIRDatabaseConnectionExhausted
        expr: |
          hikaricp_connections_active / 
          hikaricp_connections_max > 0.8
        for: 5m
        labels:
          severity: warning

7. まとめ

  • データベースの最適化 — PostgreSQL のインデックス作成、パーティショニング、接続プーリング

  • キャッシング — メタデータ/ValueSet 用の Redis、Nginx リバース プロキシ キャッシュ

  • 水平スケーリング — 複数の FHIR インスタンス + ロード バランサー + PgPool

  • モニタリング — Prometheus メトリクス、Grafana ダッシュボード、アラート ルール

  • 健康診断 — カスタム HealthIndicator、readiness/liveness プローブ

  • 医療 SLA — 稼働時間 ≥ 99.9%、P95 < 500ms、監査証跡 7 年以上