@gftdcojp/ksqldb-orm
Version:
ksqldb-orm - Server-Side TypeScript ORM for ksqlDB with enterprise security extensions
974 lines (856 loc) • 22.5 kB
Markdown
# 低レベル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()
};
};
```