すべてのプロダクト
Search
ドキュメントセンター

Realtime Compute for Apache Flink:主要パラメーター

最終更新日:Jun 21, 2026

このトピックでは、SQL 開発の主要パラメーターについて説明し、解説と使用例を提供します。

table.exec.sink.keyed-shuffle

プライマリキーを持つテーブルにデータを書き込む際に順序が乱れる問題に対処するには、table.exec.sink.keyed-shuffle パラメーターを使用してハッシュシャッフルを実行します。この操作により、同じプライマリキーを持つレコードが同じオペレーターインスタンスにルーティングされ、順序が乱れる問題を軽減できます。

注意事項

  • ハッシュシャッフル操作は、上流オペレーターが同じプライマリキーの更新レコードの順序を保証できる場合にのみ有効です。そうでない場合、この操作では順序が乱れる問題を解決できません。

  • エキスパートモードでオペレーターの並列度を変更した場合、次の並列度ルールは適用されません。

使用可能な値

  • AUTO (デフォルト):シンクの並列度が 1 ではなく、かつ上流オペレーターの並列度と異なる場合、データがシンクに流れる際に Flink がプライマリキーに対して自動的にハッシュシャッフルを実行します。

  • FORCE:シンクの並列度が 1 ではない場合、データがシンクに流れる際に Flink がプライマリキーに対して強制的にハッシュシャッフルを実行します。

  • NONE:Flink は、シンクと上流オペレーターの並列度に基づくハッシュシャッフルを実行しません。

  • パラメーターを AUTO に設定

    1. SQL ストリーミングジョブを作成し、次の SQL コードをコピーして、ジョブを [Deploy] します。このコードでは、シンクの並列度を 2 に明示的に設定しています。

      CREATE TEMPORARY TABLE s1 (
        a INT,
        b INT,
        ts TIMESTAMP(3)
      ) WITH (
        'connector'='datagen',
        'rows-per-second'='1',
        'fields.ts.kind'='random','fields.ts.max-past'='5s',
        'fields.b.kind'='random','fields.b.min'='0','fields.b.max'='10'
      );
      CREATE TEMPORARY TABLE sink (
        a INT,
        b INT,
        ts TIMESTAMP(3),
        PRIMARY KEY (a) NOT ENFORCED
      ) WITH (
        'connector'='print',
         -- sink.parallelism パラメーターを使用して、シンクの並列度を直接指定できます。
        'sink.parallelism'='2'
      );
      INSERT INTO sink SELECT * FROM s1;
      -- 動的テーブルオプションを使用して、シンクの並列度を指定することもできます。
      --INSERT INTO sink /*+ OPTIONS('sink.parallelism' = '2') */ SELECT * FROM s1;
    2. [ジョブ O&M] ページで、[デプロイ詳細] タブの [リソース設定] セクションで、[並列度] を 1 に設定します。[その他の設定][実行パラメーター設定] セクションで、table.exec.sink.keyed-shuffle パラメーターを設定したり、table.exec.sink.keyed-shuffle: AUTO を明示的に追加したりしないでください (どちらも同じ効果です)。

    3. ジョブを[開始]します。[ステータス] タブでは、上流オペレーターからシンクへのデータ接続は HASH です。

      image

  • パラメーターを FORCE に設定

    1. SQL ストリーミングジョブを作成し、次の SQL コードをコピーして、ジョブを [Deploy] します。このコードでは、シンクの並列度を明示的に指定していません。

      CREATE TEMPORARY TABLE s1 (
        a INT,
        b INT,
        ts TIMESTAMP(3)
      ) WITH (
        'connector'='datagen',
        'rows-per-second'='1',
        'fields.ts.kind'='random','fields.ts.max-past'='5s',
        'fields.b.kind'='random','fields.b.min'='0','fields.b.max'='10'
      );
      CREATE TEMPORARY TABLE sink (
        a INT,
        b INT,
        ts TIMESTAMP(3),
        PRIMARY KEY (a) NOT ENFORCED
      ) WITH (
        'connector'='print'
      );
      INSERT INTO sink
      SELECT * FROM s1;
    2. [ジョブ O&M] ページの [デプロイ詳細] タブの [リソース設定] セクションで [並列度] を 2 に設定し、[ランタイムパラメータ設定] セクションで [その他の設定]table.exec.sink.keyed-shuffle: FORCE を追加します。

    3. ジョブを [開始] した後、[ステータス] タブに移動します。シンクとアップストリームオペレーターの両方の並列度は 2 で、データ接続は HASH に変更されています。

      image

table.exec.mini-batch.size

このパラメーターは、マイクロバッチ処理でバッファリング可能なレコードの最大数を制御します。この数に達すると、計算がトリガーされてデータが出力されます。このパラメーターは、table.exec.mini-batch.enabled および table.exec.mini-batch.allow-latency と一緒に使用される場合にのみ有効になります。MiniBatch 最適化の詳細については、「MiniBatch 集計」および「MiniBatch 通常結合」をご参照ください。

注意事項

ジョブの開始前に、[パラメーター] セクションでこのパラメーターを明示的に設定しない場合、ミニバッチモードではマネージドメモリを使用してデータをバッファリングします。次のいずれかの条件で最終計算とデータ出力がトリガーされます。

  • MiniBatchAssigner オペレーターからウォーターマークメッセージを受信した場合。

  • マネージドメモリがフルになった場合。

  • チェックポイントの開始前。

  • ジョブが停止した場合。

使用可能な値

  • -1 (デフォルト):マネージドメモリを使用してデータをバッファリングすることを示します。

  • その他の負の Long 値:デフォルト設定と同じです。

  • その他の正の Long 値:ヒープメモリを使用してデータをバッファリングすることを示します。バッファリングされたレコード数がこの値 (N) に達すると、システムは出力操作を自動的にトリガーします。

  1. SQL ストリーミングジョブを作成し、次の SQL コードをコピーして、ジョブを [Deploy] します。

    CREATE TEMPORARY TABLE s1 (
      a INT,
      b INT,
      ts TIMESTAMP(3),
      PRIMARY KEY (a) NOT ENFORCED,
      WATERMARK FOR ts AS ts - INTERVAL '1' SECOND
    ) WITH (
      'connector'='datagen',
      'rows-per-second'='1',
      'fields.ts.kind'='random',
      'fields.ts.max-past'='5s',
      'fields.b.kind'='random',
      'fields.b.min'='0',
      'fields.b.max'='10'
    );
    CREATE TEMPORARY TABLE sink (
      a INT,
      b BIGINT,
      PRIMARY KEY (a) NOT ENFORCED
    ) WITH (
      'connector'='print'
    );
    INSERT INTO sink SELECT a, sum(b) FROM s1 GROUP BY a;
  2. [ジョブ O&M] ページの [デプロイ詳細] タブで、[ランタイムパラメーター設定] セクションの [その他の設定] フィールドに、table.exec.mini-batch.enabled: truetable.exec.mini-batch.allow-latency: 2s パラメーターを設定し、table.exec.mini-batch.size はデフォルト値の -1 を使用するため未設定のままにします。

  3. ジョブを [開始] します。 [ステータス] タブでは、ジョブトポロジに MiniBatchAssigner、LocalGroupAggregate、および GlobalGroupAggregate オペレーターが含まれます。

table.exec.agg.mini-batch.output-identical-enabled

State TTL が有効な場合、データが消費された後に集計結果が変わらなければ、MinibatchGlobalAgg ノードと MinibatchAgg ノードはデフォルトでダウンストリームに重複データを送信しません。これにより、ダウンストリームのステートフルノードは長期間アップストリームからデータを受信しないため、そのステートが期限切れになる可能性があります。このパラメーターは、State TTL が有効で集計結果が変わらない場合に、ダウンストリームに重複データを送信し続けるかどうかを制御します。このパラメーターを true に設定すると、このような場合に MinibatchGlobalAgg ノードと MinibatchAgg ノードがデータを送信するようになります。ジョブの集計結果が、設定された State TTL よりも頻繁に変更される場合、このパラメーターを手動で設定する必要はありません。コミュニティの問題の詳細については、「FLINK-33936」をご参照ください。

注意事項

  • このパラメーターは VVR 8.0.8 以降でのみ有効です。VVR 8.0.8 より前のバージョンでは、このパラメーターを false に設定した場合と同等の動作になります。

  • 値を false から true に変更すると、MinibatchGlobalAgg オペレーターと MinibatchAgg オペレーターから下流に送信されるデータ量が増加し、下流オペレーターの負荷が高まる可能性があります。

使用可能な値

  • false (デフォルト):ステート TTL が有効な場合、MinibatchGlobalAgg オペレーターと MinibatchAgg オペレーターは、集計結果が変化しないときに下流にデータを出力しません。

  • true:ステート TTL が有効な場合、集計結果が変化しなくても、MinibatchGlobalAgg オペレーターと MinibatchAgg オペレーターは更新 (重複) レコードを下流に引き続き出力します。

  1. SQL ストリーミングジョブを作成し、次の SQL コードをコピーして、ジョブを [Deploy] します。

    create temporary table src(
        a int,
        b string
    ) with (
        'connector' = 'datagen',
        'rows-per-second' = '10',
        'fields.a.min' = '1',
        'fields.a.max' = '1',
        'fields.b.length' = '3'
    );
    create temporary table snk(
        a int,
        max_length_b bigint
    ) with (
        'connector' = 'blackhole'
    );
    insert into snk select a, max(CHAR_LENGTH(b)) from src group by a; 
  2. [ジョブ O&M] ページの [デプロイ詳細] タブの [ランタイムパラメーター設定] エリアの [その他の設定] セクションで、table.exec.mini-batch.enabled: truetable.exec.mini-batch.allow-latency: 2s パラメーターを設定して、Minibatch Aggregate の最適化を有効にします。

  3. ジョブを[開始]します。[ステータス] タブでは、ジョブに MinibatchGlobalAggregate 演算子が含まれています。演算子の「+」記号をクリックすると、集計結果が変更されない場合に GlobalGroupAggregate 演算子がデータをダウンストリームに送信しないことを確認できます。

    オペレーターの RecordsIn が 19、RecordsOut が 1 と表示されます。これは、19 件の入力レコードから 1 件の集計出力のみが生成されたことを意味します。

  4. ジョブを停止し、[ジョブ O&M] ページの [デプロイ詳細] ページにある [実行パラメータ設定] セクションの [その他の設定] に、パラメーター table.exec.agg.mini-batch.output-identical-enabled: true を追加します。

  5. ジョブを[開始] します。[ステータス] タブでは、ジョブに MinibatchGlobalAggregate オペレーターが含まれていることを確認できます。オペレーターの「+」記号をクリックすると、集計結果が変更されていない場合でも GlobalGroupAggregate オペレーターがダウンストリームにデータを送信することを確認できます。ジョブを再起動すると、[ステータス] タブに、[GlobalGroupAggregate] オペレーターの [RecordsIn][RecordsOut] が両方とも 94 であることが表示されます。これは、table.exec.agg.mini-batch.output-identical-enabled: true が有効になっている場合、集計結果が変更されなくてもオペレーターがダウンストリームにデータを送信することを示しています。

table.exec.async-lookup.key-ordered-enabled

ディメンションテーブル結合を使用してデータエンリッチメントを行う場合、非同期モードを有効にするとスループットが向上することがよくあります。ディメンションテーブル結合では、table.exec.async-lookup.output-mode パラメーターと、入力が更新ストリームであるかどうかによって、非同期 I/O 操作の出力順序が決まります。

出力モード

更新ストリーム

非更新ストリーム

ORDERED

順序付きモード

順序付きモード

ALLOW_UNORDERED

順序付きモード

順序なしモード

表に示すとおり、更新ストリームと ALLOW_UNORDERED の組み合わせでは、順序付きモードを使用して正確性を確保しますが、スループットが一部犠牲になります。このシナリオを最適化するために、table.exec.async-lookup.key-ordered-enabled パラメーターが導入されました。このパラメーターは、更新ストリームの正確性のセマンティクスと非同期 I/O のスループット性能のバランスを取ります。ストリーム内で同じ更新キー (changelog のプライマリキーと見なすことができます) を持つメッセージは、オペレーターに入力された順序で処理されます。

説明
  • 順序付きモード: このモードはストリームの順序を保持します。結果メッセージは、非同期リクエストがトリガーされた順序 (メッセージがオペレーターに入力された順序) と同じ順序で出力されます。

  • 順序不同モード:非同期リクエストが完了するとすぐに結果メッセージが出力されるため、非同期 I/O オペレーターによってストリーム内のメッセージの順序が変更されます。詳細については、「非同期 I/O | Apache Flink」をご参照ください。

ユースケース

  • 同じ更新キーを持つメッセージが時間とともに少なくなるストリームにおいて、ディメンションテーブル結合でキーごとの処理順序を保持するためにこの最適化を使用します。

  • プライマリキーを持つ変更データキャプチャ (CDC) ストリームで、ディメンションテーブル結合によるデータエンリッチメントを行い、シンクのプライマリキーがソースのプライマリキーと一致するシンクに書き込みます。ディメンションテーブル結合の結合キーはプライマリキーとは異なり、ディメンション側の結合キーがプライマリキーです。この最適化では、更新キーとして派生した CDC プライマリキーによってシャッフルが行われます。同じシナリオで SHUFFLE_HASH 最適化を有効にする場合と比較して、この方法では、より高い並列度でシンクの前に SinkMaterializer オペレーターが生成されるのを回避します。これにより、そのオペレーターに起因する潜在的なパフォーマンスの問題、特に長時間実行中に蓄積される可能性のある大きなステートを回避できます。SinkUpsertMaterializer の詳細については、「推奨事項」をご参照ください。

  • ディメンションテーブル結合の結合キーがプライマリキーと異なり、ディメンション側の結合キーがプライマリキーであり、その後にランク演算子が続く場合、この最適化は、更新キーとして導出される CDC プライマリキーによってシャッフルを行うことで、同じシナリオで SHUFFLE_HASH 最適化を有効にする場合と比較して UpdateFastRank が RetractRank に低下するのを防ぎます。RetractRank を UpdateFastRank に最適化する方法の詳細については、「TopN 最適化手法」をご参照ください。

注意事項

  • ストリームに更新キーがない場合、行全体がキーとして使用されます。

  • 同じ更新キーが短時間に頻繁に更新される場合、同じ更新キーのレコードが厳密な順序で処理されるため、スループットが低下します。

  • 元の非同期ディメンションテーブル結合と比較すると、Key-Ordered モードではキー付きステートが導入されます。このモードの有効化/無効化はステート互換性に影響します。

  • この機能は、ディメンションテーブル結合の入力が更新ストリームであり、かつ table.exec.async-lookup.output-mode='ALLOW_UNORDERED'table.exec.async-lookup.key-ordered-enabled='true' を設定した場合にのみ、VVR 8.0.10 以降で有効になります。

使用可能な値

  • false (デフォルト):Key-Ordered モードを無効にします。

  • true:Key-Ordered モードを有効にします。

  1. 次の例では、非同期 Hologres ディメンションテーブル結合を使用します。SQL ストリーミングジョブを作成し、次の SQL コードをコピーして、ジョブを [Deploy] します。

    Hologres コネクタの詳細については、「Hologres」をご参照ください。

    CREATE TEMPORARY TABLE bid_source(
      auction  BIGINT,
      bidder  BIGINT,
      price  BIGINT,
      channel  VARCHAR,
      url  VARCHAR,
      dateTime  TIMESTAMP(3),
      extra  VARCHAR,
      proc_time AS proctime(),
      WATERMARK FOR dateTime AS dateTime - INTERVAL '4' SECOND
    ) WITH (
      'connector' = 'kafka',  -- insert-only ではないストリームコネクター
      'topic' = 'user_behavior',
      'properties.bootstrap.servers' = 'localhost:9092',
      'properties.group.id' = 'testGroup',
      'scan.startup.mode' = 'earliest-offset',
      'format' = 'canal-json'
    );
    CREATE TEMPORARY TABLE users (
        user_id STRING PRIMARY KEY NOT ENFORCED, -- プライマリキーを定義
        user_name VARCHAR(255) NOT NULL, 
        age INT NOT NULL
    ) WITH (
        'connector' = 'hologres',    -- 非同期ルックアップをサポートするコネクター
        'async' = 'true',
        'dbname' = 'holo db name', -- Hologres データベースの名前
        'tablename' = 'schema_name.table_name', -- データを受信する Hologres テーブルの名前
        'username' = 'access id', -- Alibaba Cloud アカウントの AccessKey ID
        'password' = 'access key', -- Alibaba Cloud アカウントの AccessKey Secret
        'endpoint' = 'holo vpc endpoint' -- Hologres インスタンスの VPC エンドポイント
    );
    CREATE TEMPORARY TABLE bh ( 
        auction  BIGINT,
        age INT
    ) WITH (
        'connector' = 'blackhole'
    );
    INSERT INTO bh
    SELECT
        bid_source.auction,
        u.age
    FROM bid_source
        JOIN users FOR SYSTEM_TIME AS OF bid_source.proc_time AS u
        ON bid_source.channel = u.user_id;
    
  2. [ジョブ O&M] ページの [デプロイ詳細] タブ、[ランタイムパラメーター設定] エリアの [その他の設定] セクションで、table.exec.async-lookup.output-mode='ALLOW_UNORDERED' および table.exec.async-lookup.key-ordered-enabled='true' パラメーターを設定します。

  3. ジョブを [開始] します。 [ステータス] タブでは、ジョブの async 属性が KEY_ORDERED:true であることを確認できます。

table.optimizer.window-join-enabled

このパラメーターは、ウィンドウ結合操作を有効にするかどうかを制御します。有効にすると、Flink は対応する実行計画を ウィンドウ結合として最適化します。小さなウィンドウでは、これによりステートのオーバーヘッドが削減され、パフォーマンスが向上します。通常結合と比較して、ウィンドウ結合では下流オペレーターに更新メッセージを出力しないことも可能であり、時間ウィンドウが小さい結合が必要なユースケースで有用です。

注意事項

  • ウィンドウ結合は、通常結合と比べて SQL 構文に追加の制限があり、更新ストリームはサポートされません。

  • ウィンドウ結合は、通常結合よりも出力レイテンシーが高くなります。レイテンシーはウィンドウサイズと、ソースのウォーターマークが進行する速度に依存します。

  • 有効にすると、イベントタイムの ウィンドウ結合は遅延データを破棄しますが、通常結合では破棄しません。

  • このパラメーターを変更した後は、2 つの実行方法の基盤となるステート構造に互換性がないため、既存のチェックポイントから再開できません。

使用可能な値

  1. SQL ストリーミングジョブを作成し、次の SQL コードをコピーして、SET ステートメントを使用して table.optimizer.window-join-enabled パラメーターを true に設定してから、SQL を実行して実行計画を確認します。

    SET 'table.optimizer.window-join-enabled' = 'true';
    CREATE TEMPORARY TABLE LeftTable (
      id VARCHAR,
      row_time TIMESTAMP_LTZ(3),
      num INT,
      WATERMARK FOR row_time as row_time - INTERVAL '5' SECONDS
    ) WITH (
      'connector'='datagen'
    );
    CREATE TEMPORARY TABLE RightTable (
      id VARCHAR,
      row_time TIMESTAMP_LTZ(3),
      num INT,
      WATERMARK FOR row_time as row_time - INTERVAL '10' SECONDS
    ) WITH (
      'connector'='datagen'
    );
    EXPLAIN
    SELECT L.num as L_Num, L.id as L_Id, R.num as R_Num, R.id as R_Id,
               COALESCE(L.window_start, R.window_start) as window_start,
               COALESCE(L.window_end, R.window_end) as window_end
               FROM (
                   SELECT * FROM TABLE(TUMBLE(TABLE LeftTable, DESCRIPTOR(row_time), INTERVAL '5' MINUTES))
               ) L
               JOIN (
                   SELECT * FROM TABLE(TUMBLE(TABLE RightTable, DESCRIPTOR(row_time), INTERVAL '5' MINUTES))
               ) R
               ON L.num = R.num AND L.window_start = R.window_start AND L.window_end = R.window_end;

    出力の「Optimized Execution Plan」セクションで、実行計画に WindowJoin オペレーターが含まれていることを確認できます。

    == Optimized Execution Plan ==
    Calc(select=[num AS L_Num, id AS L_Id, num0 AS R_Num, id0 AS R_Id, CASE(window_start IS NOT NULL, window_start, window_start0) AS window_start, CASE(window_end IS NOT NULL, window_end, window_end0) AS window_end])
    +- WindowJoin(leftWindow=[TUMBLE(win_start=[window_start], win_end=[window_end], size=[5 min])], rightWindow=[TUMBLE(win_start=[window_start], win_end=[window_end], size=[5 min])], joinType=[InnerJoin], where=[(num = num0)], select=[id, num, window_start, window_end, id0, num0, window_start0, window_end0])
       :- Exchange(distribution=[hash[num]])
       :  +- Calc(select=[id, num, window_start, window_end])
       :     +- WindowTableFunction(window=[TUMBLE(time_col=[row_time], size=[5 min])])
       :        +- WatermarkAssigner(rowtime=[row_time], watermark=[(row_time - 5000:INTERVAL SECOND)])
       :           +- TableSourceScan(table=[[vvp, default, LeftTable]], fields=[id, row_time, num])
       +- Exchange(distribution=[hash[num]])
          +- Calc(select=[id, num, window_start, window_end])
             +- WindowTableFunction(window=[TUMBLE(time_col=[row_time], size=[5 min])])
                +- WatermarkAssigner(rowtime=[row_time], watermark=[(row_time - 10000:INTERVAL SECOND)])
                   +- TableSourceScan(table=[[vvp, default, RightTable]], fields=[id, row_time, num])
  2. SQL コード内の SET ステートメントを修正して table.optimizer.window-join-enabled パラメーターを false に設定するか、SET ステートメントを削除してから、SQL を実行して変更後の実行計画を確認します。

    -- 'false' に設定するか、この設定句を削除します
    SET 'table.optimizer.window-join-enabled' = 'false';
    CREATE TEMPORARY TABLE LeftTable (
      id VARCHAR,
      row_time TIMESTAMP_LTZ(3),
      num INT,
      WATERMARK FOR row_time as row_time - INTERVAL '5' SECONDS
    ) WITH (
      'connector'='datagen'
    );
    CREATE TEMPORARY TABLE RightTable (
      id VARCHAR,
      row_time TIMESTAMP_LTZ(3),
      num INT,
      WATERMARK FOR row_time as row_time - INTERVAL '10' SECONDS
    ) WITH (
      'connector'='datagen'
    );
    EXPLAIN
    SELECT L.num as L_Num, L.id as L_Id, R.num as R_Num, R.id as R_Id,
               COALESCE(L.window_start, R.window_start) as window_start,
               COALESCE(L.window_end, R.window_end) as window_end
               FROM (
                   SELECT * FROM TABLE(TUMBLE(TABLE LeftTable, DESCRIPTOR(row_time), INTERVAL '5' MINUTES))
               ) L
               JOIN (
                   SELECT * FROM TABLE(TUMBLE(TABLE RightTable, DESCRIPTOR(row_time), INTERVAL '5' MINUTES))
               ) R
               ON L.num = R.num AND L.window_start = R.window_start AND L.window_end = R.window_end;

    出力の「Optimized Execution Plan」セクションには WindowJoin オペレーターが含まれなくなります。操作は通常結合になります。

    == Optimized Execution Plan ==
    Calc(select=[num AS L_Num, id AS L_Id, num0 AS R_Num, id0 AS R_Id, CASE(window_start IS NOT NULL, window_start, window_start0) AS window_start, CASE(window_end IS NOT NULL, window_end, window_end0) AS window_end])
    +- Join(joinType=[InnerJoin], where=[((num = num0) AND (window_start = window_start0) AND (window_end = window_end0))], select=[id, num, window_start, window_end, id0, num0, window_start0, window_end0], leftInputSpec=[NoUniqueKey], rightInputSpec=[NoUniqueKey])
       :- Exchange(distribution=[hash[num, window_start, window_end]])
       :  +- Calc(select=[id, num, window_start, window_end])
       :     +- WindowTableFunction(window=[TUMBLE(time_col=[row_time], size=[5 min])])
       :        +- WatermarkAssigner(rowtime=[row_time], watermark=[(row_time - 5000:INTERVAL SECOND)])
       :           +- TableSourceScan(table=[[vvp, default, LeftTable]], fields=[id, row_time, num])
       +- Exchange(distribution=[hash[num, window_start, window_end]])
          +- Calc(select=[id, num, window_start, window_end])
             +- WindowTableFunction(window=[TUMBLE(time_col=[row_time], size=[5 min])])
                +- WatermarkAssigner(rowtime=[row_time], watermark=[(row_time - 10000:INTERVAL SECOND)])
                   +- TableSourceScan(table=[[vvp, default, RightTable]], fields=[id, row_time, num])

関連ドキュメント

LocalGroupAggregate オペレーターでデータが出力されずに滞留するのはなぜですか?