UNPKG

@gftdcojp/ksqldb-orm

Version:

ksqldb-orm - Server-Side TypeScript ORM for ksqlDB with enterprise security extensions

974 lines (856 loc) 22.5 kB
# 低レベルksqlDBクライアント 低レベルksqlDBクライアントは、ksqlDBの全機能に直接アクセスできる強力なAPIです。生のSQL文を実行し、ストリーミング処理、複雑な集約、ウィンドウ関数などのksqlDBの高度な機能を活用できます。 ## 特徴 - 🚀 **フル機能** - ksqlDBのすべての機能にアクセス - 📊 **ストリーミング** - リアルタイムデータ処理 - 🔄 **プッシュクエリ** - 継続的なデータストリーミング - ⚡ **プルクエリ** - 一度だけの高速データ取得 - 🛠️ **DDL/DML** - スキーマ作成・データ操作 - 🔧 **柔軟性** - カスタムSQL文の実行 - ♾️ **自動Retention設定** - デフォルトでトピックの保持期間をinfinityに設定 ## セットアップ ```typescript import { initializeKsqlDbClient, executeQuery, executePullQuery, executePushQuery, executeDDL, executeDDLWithOptions } from '@gftdcojp/gftd-ksqldb-orm'; // クライアント初期化 initializeKsqlDbClient({ url: 'http://localhost:8088', apiKey: 'your-api-key', apiSecret: 'your-api-secret' }); ``` ## ♾️ Retention Infinity機能 このORMでは、CREATE STREAM/TABLEで作成されるすべてのトピックが**デフォルトでinfinite retention(無期限保持)**に設定されます。 ### 自動Retention設定 ```typescript // 通常のDDL実行 await executeDDL(` CREATE STREAM user_events ( user_id INT, event_type STRING, timestamp STRING ) WITH ( kafka_topic='user_events', value_format='JSON' ); `); // ↓ 自動的に'retention.ms'='-1'が追加される ``` ### カスタムRetention設定 ```typescript import { executeDDLWithOptions, type DDLExecutionOptions } from '@gftdcojp/gftd-ksqldb-orm'; // retention設定の無効化 const options: DDLExecutionOptions = { disableAutoInfinityRetention: true }; await executeDDLWithOptions(ddl, options); // カスタムretention(1日) const customOptions: DDLExecutionOptions = { customRetentionMs: 86400000 // 1日間 }; await executeDDLWithOptions(ddl, customOptions); // 追加のトピック設定 const advancedOptions: DDLExecutionOptions = { additionalWithSettings: { 'cleanup.policy': 'compact', 'segment.ms': '3600000' } }; await executeDDLWithOptions(ddl, advancedOptions); ``` ### ヘルパー関数 便利なヘルパー関数も提供されています: ```typescript import { createStreamWithInfinityRetention, createTableWithInfinityRetention } from '@gftdcojp/gftd-ksqldb-orm'; // ストリーム作成ヘルパー await createStreamWithInfinityRetention( 'user_events', { user_id: 'INT', event_type: 'STRING', timestamp: 'STRING' }, { topic: 'events', valueFormat: 'AVRO', keyField: 'user_id', partitions: 6, replicas: 3 } ); // テーブル作成ヘルパー await createTableWithInfinityRetention( 'user_summary', 'user_events', 'user_id, COUNT(*) as event_count', 'user_id' ); ``` ### スキーマ定義でのRetention設定 スキーマ定義からもretention設定を制御できます: ```typescript import { defineSchema, generateDDLFromSchema, createStreamFromSchema } from '@gftdcojp/gftd-ksqldb-orm'; import { string, int } from '@gftdcojp/gftd-ksqldb-orm/field-types'; // デフォルトでinfinite retention const userSchema = defineSchema('User', { id: int().primaryKey(), name: string().notNull(), email: string() }); // カスタムretention設定 const customSchema = defineSchema('Event', { id: string(), data: string() }, { retentionMs: 86400000, // 1日 cleanupPolicy: 'compact', partitions: 12 }); // スキーマからストリーム作成 await createStreamFromSchema('User', 'STREAM'); ``` ## DDL操作(スキーマ定義) ### ストリーム作成 ```typescript // 基本的なストリーム await executeDDL(` CREATE STREAM users_stream ( id INT, name VARCHAR, email VARCHAR, created_at VARCHAR ) WITH ( kafka_topic='users', value_format='JSON', partitions=3 ); `); // 複雑なスキーマのストリーム await executeDDL(` CREATE STREAM transaction_events ( transaction_id VARCHAR, user_id INT, amount DECIMAL(10,2), currency VARCHAR, merchant_data STRUCT< name VARCHAR, category VARCHAR, location STRUCT< lat DOUBLE, lng DOUBLE > >, tags ARRAY<VARCHAR>, metadata MAP<VARCHAR, VARCHAR>, timestamp VARCHAR ) WITH ( kafka_topic='transactions', value_format='AVRO', key='transaction_id', partitions=6, replicas=3 ); `); // タイムスタンプ列の指定 await executeDDL(` CREATE STREAM user_activities ( user_id INT, activity_type VARCHAR, page_url VARCHAR, session_id VARCHAR, event_time VARCHAR ) WITH ( kafka_topic='user_activities', value_format='JSON', timestamp='event_time', timestamp_format='yyyy-MM-dd HH:mm:ss' ); `); ``` ### テーブル作成(マテリアライズドビュー) ```typescript // 基本的な集約テーブル await executeDDL(` CREATE TABLE user_counts AS SELECT status, COUNT(*) as total_users, COUNT_DISTINCT(email) as unique_emails FROM users_stream GROUP BY status EMIT CHANGES; `); // 複雑な集約 await executeDDL(` CREATE TABLE user_stats AS SELECT id, LATEST_BY_OFFSET(name) as name, LATEST_BY_OFFSET(email) as email, COUNT(*) as event_count, EARLIEST_BY_OFFSET(created_at) as first_seen, LATEST_BY_OFFSET(created_at) as last_seen, COLLECT_LIST(activity_type) as activities FROM users_stream GROUP BY id EMIT CHANGES; `); // ウィンドウ集約テーブル await executeDDL(` CREATE TABLE sales_hourly AS SELECT WINDOWSTART as window_start, WINDOWEND as window_end, product_id, SUM(amount) as total_sales, COUNT(*) as order_count, AVG(amount) as avg_order_value, MAX(amount) as max_order_value FROM orders_stream WINDOW TUMBLING (SIZE 1 HOUR) GROUP BY product_id EMIT CHANGES; `); ``` ### JOIN操作 ```typescript // ストリーム-テーブルJOIN await executeDDL(` CREATE STREAM enriched_orders AS SELECT o.order_id, o.user_id, o.amount, u.name as user_name, u.email as user_email, u.status as user_status FROM orders_stream o LEFT JOIN users_table u ON o.user_id = u.id EMIT CHANGES; `); // ストリーム-ストリームJOIN(ウィンドウ内) await executeDDL(` CREATE STREAM user_journey AS SELECT a.user_id, a.page_url as current_page, a.timestamp as current_time, b.page_url as previous_page, b.timestamp as previous_time FROM user_activities a INNER JOIN user_activities b WITHIN 10 MINUTES ON a.user_id = b.user_id WHERE a.timestamp > b.timestamp EMIT CHANGES; `); ``` ## DML操作(データ操作) ### データ挿入 ```typescript // 単一レコード挿入 await executeQuery(` INSERT INTO users_stream (id, name, email, created_at) VALUES (1, 'John Doe', 'john@example.com', '2024-01-01T00:00:00Z'); `); // 複数レコード挿入 await executeQuery(` INSERT INTO users_stream (id, name, email, status) VALUES (1, 'Alice', 'alice@example.com', 'active'), (2, 'Bob', 'bob@example.com', 'active'), (3, 'Charlie', 'charlie@example.com', 'inactive'); `); // 複雑なデータ構造の挿入 await executeQuery(` INSERT INTO transaction_events ( transaction_id, user_id, amount, merchant_data, tags, metadata ) VALUES ( 'tx_12345', 100, 99.99, STRUCT( name := 'Best Electronics', category := 'electronics', location := STRUCT(lat := 35.6762, lng := 139.6503) ), ARRAY['online', 'credit_card'], MAP('channel' := 'web', 'campaign' := 'summer_sale') ); `); ``` ## プルクエリ(一度だけ取得) ### 基本的なプルクエリ ```typescript // 単純な選択 const result = await executePullQuery(` SELECT * FROM users_table WHERE id = 123; `); // 複雑な条件 const users = await executePullQuery(` SELECT id, name, email, status FROM users_table WHERE created_at > '2024-01-01' AND status = 'active' ORDER BY created_at DESC LIMIT 100; `); // 集約結果取得 const stats = await executePullQuery(` SELECT status, total_users, unique_emails FROM user_counts ORDER BY total_users DESC; `); ``` ### 高度なプルクエリ ```typescript // 時間範囲指定 const recentSales = await executePullQuery(` SELECT window_start, window_end, product_id, total_sales, order_count FROM sales_hourly WHERE window_start >= '2024-01-01T00:00:00' AND window_start < '2024-01-02T00:00:00' ORDER BY total_sales DESC LIMIT 10; `); // 複雑な条件とサブクエリ const topUsers = await executePullQuery(` SELECT u.id, u.name, u.event_count, u.last_seen FROM user_stats u WHERE u.event_count > ( SELECT AVG(event_count) FROM user_stats ) ORDER BY u.event_count DESC LIMIT 20; `); ``` ## プッシュクエリ(リアルタイムストリーミング) ### 基本的なストリーミング ```typescript // リアルタイムユーザーイベント監視 executePushQuery( `SELECT * FROM users_stream EMIT CHANGES;`, (data) => { console.log('New user event:', data); // リアルタイム処理ロジック processUserEvent(data); }, (error) => { console.error('Stream error:', error); // エラーハンドリング }, () => { console.log('Stream ended'); // 終了処理 } ); // 条件付きストリーミング executePushQuery( ` SELECT user_id, amount, currency, timestamp FROM transaction_events WHERE amount > 1000 EMIT CHANGES; `, (data) => { // 高額取引のアラート sendHighValueTransactionAlert(data); } ); ``` ### ウィンドウ関数を使った集約ストリーミング ```typescript // 1分間隔のアクティブユーザー数 executePushQuery( ` SELECT WINDOWSTART as window_start, WINDOWEND as window_end, COUNT_DISTINCT(user_id) as active_users, COUNT(*) as total_events FROM user_activities WINDOW TUMBLING (SIZE 1 MINUTE) GROUP BY 1 EMIT CHANGES; `, (data) => { console.log(`Active users in minute ${data.window_start}: ${data.active_users}`); updateDashboard('active_users', data); } ); // スライディングウィンドウでの移動平均 executePushQuery( ` SELECT WINDOWSTART, WINDOWEND, product_id, AVG(amount) as moving_avg_price, COUNT(*) as orders_in_window FROM orders_stream WINDOW HOPPING (SIZE 10 MINUTES, ADVANCE BY 1 MINUTE) GROUP BY product_id EMIT CHANGES; `, (data) => { updatePriceMonitoring(data); } ); // セッションウィンドウ executePushQuery( ` SELECT user_id, COUNT(*) as session_events, MIN(timestamp) as session_start, MAX(timestamp) as session_end, COLLECT_LIST(page_url) as pages_visited FROM user_activities WINDOW SESSION (60 SECONDS) GROUP BY user_id EMIT CHANGES; `, (data) => { analyzeUserSession(data); } ); ``` ## 高度なクエリパターン ### 異常検知 ```typescript // 急激な取引量増加の検知 executePushQuery( ` SELECT WINDOWSTART, user_id, COUNT(*) as transaction_count, SUM(amount) as total_amount, AVG(amount) as avg_amount FROM transaction_events WINDOW TUMBLING (SIZE 5 MINUTES) GROUP BY user_id HAVING COUNT(*) > 10 OR SUM(amount) > 5000 EMIT CHANGES; `, (data) => { // 不審な取引パターンのアラート triggerFraudAlert(data); } ); // 価格異常の検知 await executeDDL(` CREATE STREAM price_anomalies AS SELECT product_id, current_price, avg_price, (current_price - avg_price) / avg_price * 100 as price_change_percent FROM ( SELECT product_id, price as current_price, AVG(price) OVER ( PARTITION BY product_id RANGE 1 HOUR PRECEDING ) as avg_price FROM product_price_stream ) WHERE ABS((current_price - avg_price) / avg_price * 100) > 20 EMIT CHANGES; `); ``` ### イベント駆動アーキテクチャ ```typescript // カスケード処理の例 await executeDDL(` CREATE STREAM order_events AS SELECT order_id, user_id, total_amount, status, CASE WHEN total_amount > 1000 THEN 'HIGH_VALUE' WHEN total_amount > 100 THEN 'MEDIUM_VALUE' ELSE 'LOW_VALUE' END as order_tier FROM orders_stream EMIT CHANGES; `); // 各レベルでの処理 executePushQuery( `SELECT * FROM order_events WHERE order_tier = 'HIGH_VALUE' EMIT CHANGES;`, (data) => { // VIP顧客向け特別処理 processVipOrder(data); sendVipNotification(data); } ); executePushQuery( `SELECT * FROM order_events WHERE status = 'COMPLETED' EMIT CHANGES;`, (data) => { // 在庫更新トリガー updateInventory(data); // レコメンデーションエンジン更新 updateRecommendations(data); } ); ``` ## 複雑なデータ型の操作 ### 配列操作 ```typescript // 配列要素の検索 const result = await executePullQuery(` SELECT user_id, activities, ARRAY_LENGTH(activities) as activity_count FROM user_stats WHERE ARRAY_CONTAINS(activities, 'purchase'); `); // 配列の展開 await executeDDL(` CREATE STREAM user_activity_flat AS SELECT user_id, EXPLODE(activities) as activity FROM user_stats EMIT CHANGES; `); ``` ### JSON/構造体操作 ```typescript // 構造体フィールドアクセス const result = await executePullQuery(` SELECT transaction_id, merchant_data->name as merchant_name, merchant_data->location->lat as latitude, merchant_data->location->lng as longitude FROM transaction_events WHERE merchant_data->category = 'electronics'; `); // マップ操作 const metadata = await executePullQuery(` SELECT transaction_id, metadata['channel'] as channel, metadata['campaign'] as campaign FROM transaction_events WHERE metadata['channel'] IS NOT NULL; `); ``` ## スキーマ管理 ### テーブル・ストリーム情報取得 ```typescript // 全テーブル一覧 const tables = await executeQuery('LIST TABLES EXTENDED;'); // 全ストリーム一覧 const streams = await executeQuery('LIST STREAMS EXTENDED;'); // スキーマ確認 const schema = await executeQuery('DESCRIBE users_table;'); // 実行中のクエリ確認 const queries = await executeQuery('LIST QUERIES;'); ``` ### リソース管理 ```typescript // ストリーム削除 await executeDDL('DROP STREAM users_stream DELETE TOPIC;'); // テーブル削除 await executeDDL('DROP TABLE user_counts DELETE TOPIC;'); // クエリ終了 await executeQuery('TERMINATE QUERY_ID;'); // 全クエリ終了 await executeQuery('TERMINATE ALL;'); ``` ## 実用的なサンプル ### リアルタイム分析ダッシュボード ```typescript // ダッシュボード用メトリクス class RealTimeDashboard { constructor() { this.setupMetrics(); } setupMetrics() { // アクティブユーザー数 executePushQuery( ` SELECT WINDOWSTART, COUNT_DISTINCT(user_id) as active_users FROM user_activities WINDOW TUMBLING (SIZE 30 SECONDS) GROUP BY 1 EMIT CHANGES; `, (data) => { this.updateMetric('active_users', data.active_users); } ); // 収益メトリクス executePushQuery( ` SELECT WINDOWSTART, SUM(amount) as revenue, COUNT(*) as order_count, AVG(amount) as avg_order_value FROM orders_stream WHERE status = 'completed' WINDOW TUMBLING (SIZE 1 MINUTE) GROUP BY 1 EMIT CHANGES; `, (data) => { this.updateMetric('revenue', data.revenue); this.updateMetric('order_count', data.order_count); this.updateMetric('avg_order_value', data.avg_order_value); } ); // エラー率監視 executePushQuery( ` SELECT WINDOWSTART, SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) as error_count, COUNT(*) as total_requests, CAST(SUM(CASE WHEN status = 'error' THEN 1 ELSE 0 END) AS DOUBLE) / COUNT(*) * 100 as error_rate FROM api_requests_stream WINDOW TUMBLING (SIZE 1 MINUTE) GROUP BY 1 EMIT CHANGES; `, (data) => { this.updateMetric('error_rate', data.error_rate); if (data.error_rate > 5) { this.triggerAlert('High error rate detected', data); } } ); } updateMetric(name: string, value: any) { // ダッシュボード更新ロジック console.log(`Metric ${name}: ${value}`); } triggerAlert(message: string, data: any) { // アラート送信ロジック console.error(`ALERT: ${message}`, data); } } ``` ### A/Bテスト分析 ```typescript // A/Bテスト結果のリアルタイム集計 await executeDDL(` CREATE TABLE ab_test_results AS SELECT experiment_id, variant, COUNT(*) as participant_count, SUM(CASE WHEN conversion = true THEN 1 ELSE 0 END) as conversions, CAST(SUM(CASE WHEN conversion = true THEN 1 ELSE 0 END) AS DOUBLE) / COUNT(*) as conversion_rate, AVG(revenue) as avg_revenue_per_user FROM ab_test_events GROUP BY experiment_id, variant EMIT CHANGES; `); // 統計的有意性の監視 executePushQuery( ` SELECT experiment_id, COLLECT_LIST( STRUCT( variant := variant, conversion_rate := conversion_rate, participant_count := participant_count ) ) as variants FROM ab_test_results GROUP BY experiment_id EMIT CHANGES; `, (data) => { // 統計的有意性をチェック const { isSignificant, winner } = analyzeStatisticalSignificance(data.variants); if (isSignificant) { console.log(`Experiment ${data.experiment_id} has significant results. Winner: ${winner}`); // 実験終了の提案 suggestExperimentConclusion(data.experiment_id, winner); } } ); ``` ### ログ分析とアラート ```typescript // エラーログのパターン検出 await executeDDL(` CREATE STREAM error_patterns AS SELECT WINDOWSTART, error_type, service_name, COUNT(*) as error_count, COLLECT_LIST(error_message) as error_messages FROM application_logs WHERE log_level = 'ERROR' WINDOW TUMBLING (SIZE 5 MINUTES) GROUP BY error_type, service_name HAVING COUNT(*) > 10 EMIT CHANGES; `); // アラート生成 executePushQuery( `SELECT * FROM error_patterns EMIT CHANGES;`, (data) => { const alert = { type: 'ERROR_SPIKE', service: data.service_name, errorType: data.error_type, count: data.error_count, timeWindow: data.WINDOWSTART, samples: data.error_messages.slice(0, 3) }; // アラートシステムに送信 sendAlert(alert); // 自動スケーリングトリガー if (data.error_count > 50) { triggerAutoScaling(data.service_name); } } ); ``` ## パフォーマンス最適化 ### クエリ最適化のベストプラクティス ```typescript // 1. 適切なパーティショニング await executeDDL(` CREATE STREAM optimized_events ( user_id INT, event_data VARCHAR, timestamp VARCHAR ) WITH ( kafka_topic='events', value_format='JSON', partitions=12, -- 適切なパーティション数 key='user_id' -- 効率的なパーティショニング ); `); // 2. インデックス的な使用パターン await executeDDL(` CREATE TABLE user_lookup AS SELECT id, LATEST_BY_OFFSET(name) as name, LATEST_BY_OFFSET(email) as email FROM users_stream GROUP BY id EMIT CHANGES; `); // 3. 効率的なウィンドウサイズ // 小さすぎる → 高頻度更新でオーバーヘッド // 大きすぎる → メモリ使用量増加 await executeDDL(` CREATE TABLE balanced_metrics AS SELECT WINDOWSTART, metric_name, AVG(value) as avg_value, COUNT(*) as count FROM metrics_stream WINDOW TUMBLING (SIZE 1 MINUTE) -- バランスの取れたウィンドウサイズ GROUP BY metric_name EMIT CHANGES; `); ``` ## エラーハンドリングとトラブルシューティング ### 一般的なエラーパターン ```typescript // リトライ機構付きクエリ実行 const executeWithRetry = async (query: string, maxRetries: number = 3) => { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await executeQuery(query); } catch (error: any) { console.error(`Attempt ${attempt} failed:`, error.message); if (attempt === maxRetries) { throw new Error(`Query failed after ${maxRetries} attempts: ${error.message}`); } // 指数バックオフ const delay = Math.pow(2, attempt) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); } } }; // 接続状態監視 const monitorConnection = () => { const checkInterval = setInterval(async () => { try { await executeQuery('LIST STREAMS;'); console.log('ksqlDB connection healthy'); } catch (error) { console.error('ksqlDB connection lost:', error); // 再接続ロジック reconnectKsqlDb(); } }, 30000); // 30秒間隔 return checkInterval; }; ``` ### デバッグとモニタリング ```typescript // クエリ実行時間測定 const executeWithTiming = async (query: string) => { const startTime = Date.now(); try { const result = await executeQuery(query); const duration = Date.now() - startTime; console.log(`Query executed in ${duration}ms`); return result; } catch (error) { const duration = Date.now() - startTime; console.error(`Query failed after ${duration}ms:`, error); throw error; } }; // システムメトリクス監視 const getSystemMetrics = async () => { const serverInfo = await executeQuery('SHOW PROPERTIES;'); const queryStatus = await executeQuery('LIST QUERIES;'); return { serverInfo, activeQueries: queryStatus.length, timestamp: new Date().toISOString() }; }; ```