@@ -3112,3 +3112,119 @@ def workflow_ddl_detection_with_id_pool(c: Composition) -> None:
31123112 > SELECT * FROM pool_mv;
31133113 1
31143114 """ ))
3115+
3116+
3117+ def workflow_ddl_detection_ephemeral_items (c : Composition ) -> None :
3118+ """Verify that temporary items do not count as reactable DDL in preflight.
3119+
3120+ Temporary items are durable catalog items tagged with their owning
3121+ session's UUID and draw ids from the normal user-id allocator.
3122+ Ensure that creation of them during 0dt preflight does not halt the
3123+ read-only environment.
3124+ """
3125+ c .down (destroy_volumes = True )
3126+ c .up ("mz_old" )
3127+
3128+ PREFLIGHT_STARTED = "waiting for deployment to be caught up"
3129+
3130+ def count_preflight_starts () -> int :
3131+ """Count mz_new boots via the preflight start line in its log."""
3132+ logs = c .invoke ("logs" , "mz_new" , capture = True ).stdout
3133+ return sum (PREFLIGHT_STARTED in line for line in logs .splitlines ())
3134+
3135+ def await_preflight_start () -> None :
3136+ deadline = time .time () + 120
3137+ while time .time () < deadline :
3138+ if count_preflight_starts () >= 1 :
3139+ return
3140+ time .sleep (0.5 )
3141+ raise RuntimeError ("timed out waiting for mz_new preflight to start" )
3142+
3143+ # The DDL check defaults to every 5 minutes plus once right before
3144+ # ready-to-promote. Tighten it so the temporary items below sit through
3145+ # many checks. Read at mz_new's boot from the catalog.
3146+ c .sql (
3147+ """
3148+ ALTER SYSTEM SET with_0dt_deployment_ddl_check_interval = '1s';
3149+ ALTER SYSTEM SET cluster = quickstart;
3150+ """ ,
3151+ service = "mz_old" ,
3152+ port = 6877 ,
3153+ user = "mz_system" ,
3154+ )
3155+
3156+ # Start mz_new in read-only mode (deploy_generation=1) and wait for it
3157+ # to start the preflight process.
3158+ c .up ("mz_new" )
3159+ await_preflight_start ()
3160+
3161+ # A session on the leader creates the temporary items. The connection
3162+ # stays open so the items stay durable.
3163+ conn = c .sql_connection (service = "mz_old" )
3164+ cur = conn .cursor ()
3165+ cur .execute ("CREATE TEMPORARY TABLE temp_t (a int)" )
3166+ cur .execute ("CREATE TEMPORARY VIEW temp_v AS SELECT * FROM temp_t" )
3167+ cur .execute ("INSERT INTO temp_t VALUES (1)" )
3168+
3169+ # Prove the temporary items are durable catalog rows on the leader while
3170+ # mz_new's checks tick
3171+ ephemeral = c .sql_query (
3172+ """SELECT count(*) FROM mz_internal.mz_catalog_raw
3173+ WHERE data->>'kind' = 'Item'
3174+ AND data->'value'->>'ephemeral_owner_session' IS NOT NULL""" ,
3175+ service = "mz_old" ,
3176+ port = 6877 ,
3177+ user = "mz_system" ,
3178+ )
3179+ assert ephemeral == [(2 ,)], f"temporary items are not durable: { ephemeral } "
3180+
3181+ # the temporary items must exist while mz_new is still checking for DDL
3182+ # i.e. before it announces ready. if we're caught up before we've created
3183+ # temporary items, fail loudly.
3184+ deadline = time .time () + 120
3185+ status = None
3186+ while time .time () < deadline :
3187+ try :
3188+ status = _leader_status (c , "mz_new" )
3189+ break
3190+ except Exception :
3191+ time .sleep (1 )
3192+ assert (
3193+ status == DeploymentStatus .INITIALIZING .value
3194+ ), f"mz_new reached status { status } before the temporary items were created"
3195+
3196+ # Sit through several 1s-interval DDL checks with the temporary items in
3197+ # the catalog, then let mz_new run the final check on its way to
3198+ # ready-to-promote. Assert we only see one preflight start throughout
3199+ # promotion which means we never halted.
3200+ time .sleep (5 )
3201+ assert (
3202+ count_preflight_starts () == 1
3203+ ), "mz_new rebooted with only temporary items created"
3204+ c .await_mz_deployment_status (DeploymentStatus .READY_TO_PROMOTE , "mz_new" )
3205+ assert (
3206+ count_preflight_starts () == 1
3207+ ), "mz_new rebooted on the final DDL check with only temporary items created"
3208+
3209+ c .promote_mz ("mz_new" )
3210+ c .await_mz_deployment_status (DeploymentStatus .IS_LEADER , "mz_new" , sleep_time = None )
3211+
3212+ # The takeover opened the catalog with write intent, which fences the old
3213+ # leader (killing the session that owned the temporary items) and
3214+ # reclaims every ephemeral item. Only mz_catalog_raw shows whether the
3215+ # durable rows themselves are gone.
3216+ ephemeral = c .sql_query (
3217+ """SELECT count(*) FROM mz_internal.mz_catalog_raw
3218+ WHERE data->>'kind' = 'Item'
3219+ AND data->'value'->>'ephemeral_owner_session' IS NOT NULL""" ,
3220+ service = "mz_new" ,
3221+ port = 6877 ,
3222+ user = "mz_system" ,
3223+ )
3224+ assert ephemeral == [(0 ,)], f"ephemeral items survived promotion: { ephemeral } "
3225+
3226+ # The old leader died with the session's socket; closing is bookkeeping.
3227+ try :
3228+ conn .close ()
3229+ except Exception :
3230+ pass
0 commit comments