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