এয়ারফ্লোতে ডায়নামিক ওয়ার্কফ্লো তৈরির সঠিক উপায়


98

সমস্যা

ওয়ার্কফ্লো তৈরির জন্য এয়ারফ্লোতে কি এমন কোনও উপায় আছে যে টাস্ক এ শেষ না হওয়া অবধি বি * কার্যের সংখ্যা অজানা? আমি সাবড্যাগগুলি দেখেছি তবে দেখে মনে হচ্ছে এটি কেবলমাত্র ড্যাগ তৈরির সময় নির্ধারিত কাজগুলির একটি স্থির সেট নিয়ে কাজ করতে পারে।

কাজ কি ট্রিগার ট্রিগার করবে? এবং যদি আপনি দয়া করে একটি উদাহরণ দিতে পারে।

আমার একটি সমস্যা আছে যেখানে টাস্ক এ সম্পন্ন না হওয়া পর্যন্ত টাস্ক সি গণনা করার জন্য যে টাস্ক বিয়ের প্রয়োজন হবে তা জানা অসম্ভব। প্রতিটি টাস্ক বি। গণনা করতে কয়েক ঘন্টা সময় নেয় এবং একত্রিত করা যায় না।

              |---> Task B.1 --|
              |---> Task B.2 --|
 Task A ------|---> Task B.3 --|-----> Task C
              |       ....     |
              |---> Task B.N --|

আইডিয়া # 1

আমি এই সমাধানটি পছন্দ করি না কারণ আমাকে একটি ব্লকিং বহিরাগত টাস্ক সেন্সর তৈরি করতে হবে এবং সমস্ত টাস্ক বি * শেষ করতে 2-24 ঘন্টা সময় লাগবে। সুতরাং আমি এটিকে একটি কার্যকর সমাধান হিসাবে বিবেচনা করি না। অবশ্যই একটি সহজ উপায় আছে? বা এয়ারফ্লো কি এর জন্য ডিজাইন করা হয়নি?

Dag 1
Task A -> TriggerDagRunOperator(Dag 2) -> ExternalTaskSensor(Dag 2, Task Dummy B) -> Task C

Dag 2 (Dynamically created DAG though python_callable in TriggerDagrunOperator)
               |-- Task B.1 --|
               |-- Task B.2 --|
Task Dummy A --|-- Task B.3 --|-----> Task Dummy B
               |     ....     |
               |-- Task B.N --|

সম্পাদনা 1:

এখনও হিসাবে এই প্রশ্নের এখনও একটি দুর্দান্ত উত্তর নেই । সমাধান অনুসন্ধানের জন্য আমার বেশ কয়েকজন লোক যোগাযোগ করেছে।


সমস্ত কাজ বি * কি একই রকম, সেগুলি লুপে তৈরি করা যেতে পারে?
ড্যানিয়েল লি

হ্যাঁ সমস্ত বি। টাস্ক এ টাস্ক এ শেষ হয়ে গেলে একটি লুপে দ্রুত তৈরি করা যায়। টাস্ক এ সম্পূর্ণ হতে প্রায় 2 ঘন্টা সময় নেয়।
কস্টরোক

আপনি কি সমস্যার সমাধান খুঁজে পেয়েছেন? আপনি এটি পোস্ট করতে আপত্তি হতে পারে?
ড্যানিয়েল দুবভস্কি

4
আইডিয়া # 1 এর জন্য একটি দরকারী সংস্থান: সংযুক্ত in.com/pulse/…
জুয়ান রিয়াজা

উত্তর:


33

কোনও সাবড্যাগ ছাড়াই অনুরূপ অনুরোধের মাধ্যমে আমি এটি কীভাবে করেছি তা এখানে:

প্রথমে এমন একটি পদ্ধতি তৈরি করুন যা আপনি চান মানগুলি ফিরিয়ে দেয়

def values_function():
     return values

পরবর্তী পদ্ধতিটি গতিশীলভাবে কাজগুলি তৈরি করবে তা তৈরি করুন:

def group(number, **kwargs):
        #load the values if needed in the command you plan to execute
        dyn_value = "{{ task_instance.xcom_pull(task_ids='push_func') }}"
        return BashOperator(
                task_id='JOB_NAME_{}'.format(number),
                bash_command='script.sh {} {}'.format(dyn_value, number),
                dag=dag)

এবং তারপরে তাদের একত্রিত করুন:

push_func = PythonOperator(
        task_id='push_func',
        provide_context=True,
        python_callable=values_function,
        dag=dag)

complete = DummyOperator(
        task_id='All_jobs_completed',
        dag=dag)

for i in values_function():
        push_func >> group(i) >> complete

মানগুলি কোথায় সংজ্ঞায়িত হয়?
সন্ন্যাসী

11
পরিবর্তে for i in values_function()আমি এমন কিছু আশা করব for i in push_func_output। সমস্যা হ'ল আমি আউটপুটটি গতিশীলভাবে পাওয়ার কোনও উপায় খুঁজে পাচ্ছি না। পাইথন অপারেটরের আউটপুট এক্সিকিউশনের পরে এক্সকমে থাকবে তবে আমি জানি না যে আমি এটি ডিএজি সংজ্ঞা থেকে উল্লেখ করতে পারি কিনা।
এনা

@ এনা আপনি কি এটি অর্জনের জন্য কোনও উপায় খুঁজে পেয়েছেন?
জুনিয়র

4
@ ফিল্ডস নীচে আমার উত্তর দেখুন
এনা

4
যদি আমাদের লুপের মধ্যে কয়েকটি ধাপ নির্ভরশীল পদক্ষেপগুলি সম্পাদন করতে হয়? groupফাংশনের মধ্যে একটি দ্বিতীয় নির্ভরতা শৃঙ্খলা থাকবে ?
কোডিংইনসার্কেলস

12

আমি পূর্ববর্তী কার্যগুলির ফলাফলের ভিত্তিতে ওয়ার্কফ্লো তৈরির একটি উপায় বের করেছি।
মূলত আপনি যা করতে চান তা হ'ল নিম্নলিখিত দুটি সাবড্যাগ রয়েছে:

  1. এক্সকম কমপ্লেক্স সাবড্যাগে একটি তালিকা (বা যা আপনার পরে ডায়নামিক ওয়ার্কফ্লো তৈরি করতে হবে) যা প্রথমে কার্যকর করা হয় (টেস্ট 1.py দেখুন def return_list())
  2. আপনার দ্বিতীয় সাবড্যাগে প্যারামিটার হিসাবে মূল ডাগ অজানাটি পাস করুন
  3. এখন আপনার কাছে যদি প্রধান ড্যাগ অবজেক্ট থাকে তবে আপনি এটির কার্যের উদাহরণগুলির তালিকা পেতে এটি ব্যবহার করতে পারেন। টাস্কের উদাহরণগুলির তালিকা থেকে আপনি বর্তমান রানের কোনও কাজ ফিল্টার করে ব্যবহার করে parent_dag.get_task_instances(settings.Session, start_date=parent_dag.get_active_runs()[-1])[-1]) এটির মাধ্যমে সম্ভবত আরও ফিল্টার যুক্ত করা যেতে পারে।
  4. সেই কাজের উদাহরণ সহ, আপনি প্রথম সাবড্যাগের একটিতে ড্যাগ_আইডি নির্দিষ্ট করে আপনার প্রয়োজনীয় মান পেতে xcom টান ব্যবহার করতে পারেন: dag_id='%s.%s' % (parent_dag_name, 'test1')
  5. আপনার কাজগুলি গতিশীলভাবে তৈরি করতে তালিকা / মানটি ব্যবহার করুন

এখন আমি আমার স্থানীয় এয়ারফ্লো ইনস্টলেশনতে এটি পরীক্ষা করেছি এবং এটি দুর্দান্ত কাজ করে। আমি জানি না যে একই সময়ে দাগের একাধিক উদাহরণ চলতে থাকলে এক্সকমের টান অংশে কোনও সমস্যা হবে কিনা তবে আপনি সম্ভবত এক্সকমকে চিহ্নিত করার জন্য একটি অনন্য কী বা এর মতো কিছু ব্যবহার করতে চাইবেন আপনি চান মান। বর্তমানের প্রধান ডাগের একটি নির্দিষ্ট টাস্কটি পেতে 100% নিশ্চিত হওয়ার জন্য একটিটি সম্ভবত 3. পদক্ষেপটি অনুকূলিত করতে পারে, তবে আমার ব্যবহারের জন্য এটি যথেষ্ট ভাল সম্পাদন করে, আমি মনে করি এক্সকম_পুল ব্যবহার করতে একজনকে কেবল একটি টাস্ক_ইনস্ট্যান্স অবজেক্টের প্রয়োজন।

এছাড়াও আমি প্রতিটি মৃত্যুদন্ড কার্যকর হওয়ার আগে প্রথম সাবড্যাগের জন্য এক্সকোমগুলি পরিষ্কার করি, যাতে নিশ্চিত হয়ে যায় যে আমি ভুলবশত কোনও ভুল মূল্য না পাই।

আমি ব্যাখ্যা করতে বেশ খারাপ, তাই আমি আশা করি নিম্নলিখিত কোডটি সবকিছু পরিষ্কার করে দেবে:

test1.py

from airflow.models import DAG
import logging
from airflow.operators.python_operator import PythonOperator
from airflow.operators.postgres_operator import PostgresOperator

log = logging.getLogger(__name__)


def test1(parent_dag_name, start_date, schedule_interval):
    dag = DAG(
        '%s.test1' % parent_dag_name,
        schedule_interval=schedule_interval,
        start_date=start_date,
    )

    def return_list():
        return ['test1', 'test2']

    list_extract_folder = PythonOperator(
        task_id='list',
        dag=dag,
        python_callable=return_list
    )

    clean_xcoms = PostgresOperator(
        task_id='clean_xcoms',
        postgres_conn_id='airflow_db',
        sql="delete from xcom where dag_id='{{ dag.dag_id }}'",
        dag=dag)

    clean_xcoms >> list_extract_folder

    return dag

test2.py

from airflow.models import DAG, settings
import logging
from airflow.operators.dummy_operator import DummyOperator

log = logging.getLogger(__name__)


def test2(parent_dag_name, start_date, schedule_interval, parent_dag=None):
    dag = DAG(
        '%s.test2' % parent_dag_name,
        schedule_interval=schedule_interval,
        start_date=start_date
    )

    if len(parent_dag.get_active_runs()) > 0:
        test_list = parent_dag.get_task_instances(settings.Session, start_date=parent_dag.get_active_runs()[-1])[-1].xcom_pull(
            dag_id='%s.%s' % (parent_dag_name, 'test1'),
            task_ids='list')
        if test_list:
            for i in test_list:
                test = DummyOperator(
                    task_id=i,
                    dag=dag
                )

    return dag

এবং প্রধান কর্মপ্রবাহ:

পরীক্ষা.পি

from datetime import datetime
from airflow import DAG
from airflow.operators.subdag_operator import SubDagOperator
from subdags.test1 import test1
from subdags.test2 import test2

DAG_NAME = 'test-dag'

dag = DAG(DAG_NAME,
          description='Test workflow',
          catchup=False,
          schedule_interval='0 0 * * *',
          start_date=datetime(2018, 8, 24))

test1 = SubDagOperator(
    subdag=test1(DAG_NAME,
                 dag.start_date,
                 dag.schedule_interval),
    task_id='test1',
    dag=dag
)

test2 = SubDagOperator(
    subdag=test2(DAG_NAME,
                 dag.start_date,
                 dag.schedule_interval,
                 parent_dag=dag),
    task_id='test2',
    dag=dag
)

test1 >> test2

এয়ারফ্লোতে 1.9 এ ডিএজি ফোল্ডারে যুক্ত হওয়ার পরে লোড হয়নি, আমি কিছু হারিয়েছি?
অ্যান্টনি কানে

@ অ্যান্থনিকেন আপনি কি আপনার ড্যাগ ফোল্ডারে সাবড্যাগস নামে একটি ফোল্ডারে test1.py এবং test2.py রেখেছিলেন?
ক্রিস্টোফার বেক

আমি হ্যাঁ করেছি। উভয় ফাইলকে সাবড্যাগে অনুলিপি করে টেস্ট.পিকে ড্যাগ ফোল্ডারে রেখেছেন, তবুও এই ত্রুটিটি পান। ভাঙা ডিএজি: [/ home/airflow/gcs/dags/test.py] সাবড্যাগস.টেষ্ট 1 নামে কোনও মডিউল নেই নোট আমি গুগল ক্লাউড সুরকার (গুগলের পরিচালিত এয়ারফ্লো ১.৯.০) ব্যবহার করছি
অ্যান্টনি কেইন

@ অ্যান্থনিকিনে আপনি কি লগগুলিতে এইমাত্র ত্রুটিটি দেখছেন? সাবড্যাগের একটি সংকলন ত্রুটি থাকার কারণে ভাঙা ডিএজি হতে পারে।
ক্রিস্টোফার বেক

4
হাই ক্রিস্টোফার বেক আমি আমার ভুলটি _ _init_ _.pyসাবড্যাগস ফোল্ডারে যুক্ত করার দরকার খুঁজে পেয়েছি । ছদ্মবেশী ত্রুটি
অ্যান্টনি কেইন

11

হ্যাঁ এটি সম্ভব আমি একটি উদাহরণ তৈরি করেছি ডিএজি যা এটি প্রদর্শন করে।

import airflow
from airflow.operators.python_operator import PythonOperator
import os
from airflow.models import Variable
import logging
from airflow import configuration as conf
from airflow.models import DagBag, TaskInstance
from airflow import DAG, settings
from airflow.operators.bash_operator import BashOperator

main_dag_id = 'DynamicWorkflow2'

args = {
    'owner': 'airflow',
    'start_date': airflow.utils.dates.days_ago(2),
    'provide_context': True
}

dag = DAG(
    main_dag_id,
    schedule_interval="@once",
    default_args=args)


def start(*args, **kwargs):

    value = Variable.get("DynamicWorkflow_Group1")
    logging.info("Current DynamicWorkflow_Group1 value is " + str(value))


def resetTasksStatus(task_id, execution_date):
    logging.info("Resetting: " + task_id + " " + execution_date)

    dag_folder = conf.get('core', 'DAGS_FOLDER')
    dagbag = DagBag(dag_folder)
    check_dag = dagbag.dags[main_dag_id]
    session = settings.Session()

    my_task = check_dag.get_task(task_id)
    ti = TaskInstance(my_task, execution_date)
    state = ti.current_state()
    logging.info("Current state of " + task_id + " is " + str(state))
    ti.set_state(None, session)
    state = ti.current_state()
    logging.info("Updated state of " + task_id + " is " + str(state))


def bridge1(*args, **kwargs):

    # You can set this value dynamically e.g., from a database or a calculation
    dynamicValue = 2

    variableValue = Variable.get("DynamicWorkflow_Group2")
    logging.info("Current DynamicWorkflow_Group2 value is " + str(variableValue))

    logging.info("Setting the Airflow Variable DynamicWorkflow_Group2 to " + str(dynamicValue))
    os.system('airflow variables --set DynamicWorkflow_Group2 ' + str(dynamicValue))

    variableValue = Variable.get("DynamicWorkflow_Group2")
    logging.info("Current DynamicWorkflow_Group2 value is " + str(variableValue))

    # Below code prevents this bug: https://issues.apache.org/jira/browse/AIRFLOW-1460
    for i in range(dynamicValue):
        resetTasksStatus('secondGroup_' + str(i), str(kwargs['execution_date']))


def bridge2(*args, **kwargs):

    # You can set this value dynamically e.g., from a database or a calculation
    dynamicValue = 3

    variableValue = Variable.get("DynamicWorkflow_Group3")
    logging.info("Current DynamicWorkflow_Group3 value is " + str(variableValue))

    logging.info("Setting the Airflow Variable DynamicWorkflow_Group3 to " + str(dynamicValue))
    os.system('airflow variables --set DynamicWorkflow_Group3 ' + str(dynamicValue))

    variableValue = Variable.get("DynamicWorkflow_Group3")
    logging.info("Current DynamicWorkflow_Group3 value is " + str(variableValue))

    # Below code prevents this bug: https://issues.apache.org/jira/browse/AIRFLOW-1460
    for i in range(dynamicValue):
        resetTasksStatus('thirdGroup_' + str(i), str(kwargs['execution_date']))


def end(*args, **kwargs):
    logging.info("Ending")


def doSomeWork(name, index, *args, **kwargs):
    # Do whatever work you need to do
    # Here I will just create a new file
    os.system('touch /home/ec2-user/airflow/' + str(name) + str(index) + '.txt')


starting_task = PythonOperator(
    task_id='start',
    dag=dag,
    provide_context=True,
    python_callable=start,
    op_args=[])

# Used to connect the stream in the event that the range is zero
bridge1_task = PythonOperator(
    task_id='bridge1',
    dag=dag,
    provide_context=True,
    python_callable=bridge1,
    op_args=[])

DynamicWorkflow_Group1 = Variable.get("DynamicWorkflow_Group1")
logging.info("The current DynamicWorkflow_Group1 value is " + str(DynamicWorkflow_Group1))

for index in range(int(DynamicWorkflow_Group1)):
    dynamicTask = PythonOperator(
        task_id='firstGroup_' + str(index),
        dag=dag,
        provide_context=True,
        python_callable=doSomeWork,
        op_args=['firstGroup', index])

    starting_task.set_downstream(dynamicTask)
    dynamicTask.set_downstream(bridge1_task)

# Used to connect the stream in the event that the range is zero
bridge2_task = PythonOperator(
    task_id='bridge2',
    dag=dag,
    provide_context=True,
    python_callable=bridge2,
    op_args=[])

DynamicWorkflow_Group2 = Variable.get("DynamicWorkflow_Group2")
logging.info("The current DynamicWorkflow value is " + str(DynamicWorkflow_Group2))

for index in range(int(DynamicWorkflow_Group2)):
    dynamicTask = PythonOperator(
        task_id='secondGroup_' + str(index),
        dag=dag,
        provide_context=True,
        python_callable=doSomeWork,
        op_args=['secondGroup', index])

    bridge1_task.set_downstream(dynamicTask)
    dynamicTask.set_downstream(bridge2_task)

ending_task = PythonOperator(
    task_id='end',
    dag=dag,
    provide_context=True,
    python_callable=end,
    op_args=[])

DynamicWorkflow_Group3 = Variable.get("DynamicWorkflow_Group3")
logging.info("The current DynamicWorkflow value is " + str(DynamicWorkflow_Group3))

for index in range(int(DynamicWorkflow_Group3)):

    # You can make this logic anything you'd like
    # I chose to use the PythonOperator for all tasks
    # except the last task will use the BashOperator
    if index < (int(DynamicWorkflow_Group3) - 1):
        dynamicTask = PythonOperator(
            task_id='thirdGroup_' + str(index),
            dag=dag,
            provide_context=True,
            python_callable=doSomeWork,
            op_args=['thirdGroup', index])
    else:
        dynamicTask = BashOperator(
            task_id='thirdGroup_' + str(index),
            bash_command='touch /home/ec2-user/airflow/thirdGroup_' + str(index) + '.txt',
            dag=dag)

    bridge2_task.set_downstream(dynamicTask)
    dynamicTask.set_downstream(ending_task)

# If you do not connect these then in the event that your range is ever zero you will have a disconnection between your stream
# and your tasks will run simultaneously instead of in your desired stream order.
starting_task.set_downstream(bridge1_task)
bridge1_task.set_downstream(bridge2_task)
bridge2_task.set_downstream(ending_task)

আপনি ডিএজি চালানোর আগে এই তিনটি এয়ারফ্লো ভেরিয়েবল তৈরি করুন

airflow variables --set DynamicWorkflow_Group1 1

airflow variables --set DynamicWorkflow_Group2 0

airflow variables --set DynamicWorkflow_Group3 0

আপনি দেখতে পাবেন যে এই থেকে ডিএজি চলে গেছে

এখানে চিত্র বর্ণনা লিখুন

এটি চালানোর পরে এটি

এখানে চিত্র বর্ণনা লিখুন

আপনি ডায়ামিক ওয়ার্কফ্লো অন এয়ারফ্লো তৈরির বিষয়ে আমার নিবন্ধে এই ড্যাগের আরও তথ্য দেখতে পারেন


4
তবে আপনার যদি এই ড্যাগের একাধিক ড্যাগআরুন থাকে তবে কী হবে। তারা সবাই কি একই ভেরিয়েবলগুলি ভাগ করে দেয়?
মার্চ-কে

4
হ্যাঁ তারা একই পরিবর্তনশীল ব্যবহার করবে; আমি আমার নিবন্ধে একেবারে শেষে এটিকে সম্বোধন করছি। আপনার গতিশীল পরিবর্তনশীল তৈরি করতে হবে এবং ভেরিয়েবল নামে ড্যাগ রান আইডি ব্যবহার করতে হবে। আমার উদাহরণটি কেবল গতিশীল সম্ভাবনা প্রদর্শনের জন্য সহজ তবে আপনাকে এটির উত্পাদন মানের তৈরি করতে হবে :)
কাইল ব্রাইডেনস্টাইন

গতিশীল কাজগুলি তৈরি করার সময় সেতুগুলি কী প্রয়োজনীয়? আপনার নিবন্ধটি পুরো মুহুর্তে পুরোপুরি পড়বে, তবে জানতে চেয়েছিল। আমি এখনই একটি উজানের কাজটির উপর ভিত্তি করে আ ডায়নামিক টাস্ক তৈরির সাথে লড়াই করছি এবং আমি কোথায় ভুল হয়ে গেছি তা নির্ধারণের জন্য শুরু করছি। আমার বর্তমান সমস্যাটি হ'ল কোনও কারণে আমি ডিএজি-কে ডিএজি-ব্যাগে সিঙ্ক করতে পারি না। আমি যখন মডিউলটিতে একটি স্ট্যাটিক তালিকা ব্যবহার করছিলাম তখন আমার ডিএজি সিঙ্ক হয়েছিল, তবে যখন আমি স্ট্র্যাটিক তালিকাটি একটি উজানের কাজ থেকে তৈরি করতে স্যুইচ করেছি তখন বন্ধ হয়ে গেল।
লুসিড_গুজ

এটি খুব চালাক
jvans

4
@ জভানস ধন্যবাদ এটি চতুর কিন্তু উত্পাদন মানের না
কাইল ব্রিজেনস্টাইন

6

ওএ: "এয়ারফ্লোতে কোনও ওয়ার্কফ্লো তৈরি করার কোনও উপায় আছে যে টাস্ক এ শেষ না হওয়া অবধি বি * কাজের সংখ্যা অজানা?"

সংক্ষিপ্ত উত্তর না হয়। এয়ারফ্লোটি ড্যাগ প্রবাহ চালানো শুরু করার আগে তৈরি করবে।

এটি বলেছিল যে আমরা একটি সাধারণ সিদ্ধান্তে পৌঁছেছি, তা হ'ল আমাদের এমন প্রয়োজন নেই। আপনি যখন কিছু কাজের সমান্তরাল করতে চান তখন আপনার যে সংস্থানগুলি উপলব্ধ রয়েছে তার মূল্যায়ন করা উচিত এবং প্রক্রিয়া করার জন্য আইটেমের সংখ্যা নয়।

আমরা এটি এর মতো করেছিলাম: আমরা গতিশীলভাবে একটি নির্দিষ্ট সংখ্যা তৈরি করি, বলুন 10, যা কাজটি বিভক্ত করবে। উদাহরণস্বরূপ, যদি আমাদের 100 টি ফাইল প্রক্রিয়া করতে হয় তবে প্রতিটি টাস্ক তাদের 10 টি প্রক্রিয়া করবে। আমি আজ পরে কোড পোস্ট করব।

হালনাগাদ

বিলম্বের জন্য দুঃখিত, কোডটি এখানে।

from datetime import datetime, timedelta

import airflow
from airflow.operators.dummy_operator import DummyOperator

args = {
    'owner': 'airflow',
    'depends_on_past': False,
    'start_date': datetime(2018, 1, 8),
    'email': ['myemail@gmail.com'],
    'email_on_failure': True,
    'email_on_retry': True,
    'retries': 1,
    'retry_delay': timedelta(seconds=5)
}

dag = airflow.DAG(
    'parallel_tasks_v1',
    schedule_interval="@daily",
    catchup=False,
    default_args=args)

# You can read this from variables
parallel_tasks_total_number = 10

start_task = DummyOperator(
    task_id='start_task',
    dag=dag
)


# Creates the tasks dynamically.
# Each one will elaborate one chunk of data.
def create_dynamic_task(current_task_number):
    return DummyOperator(
        provide_context=True,
        task_id='parallel_task_' + str(current_task_number),
        python_callable=parallelTask,
        # your task will take as input the total number and the current number to elaborate a chunk of total elements
        op_args=[current_task_number, int(parallel_tasks_total_number)],
        dag=dag)


end = DummyOperator(
    task_id='end',
    dag=dag)

for page in range(int(parallel_tasks_total_number)):
    created_task = create_dynamic_task(page)
    start_task >> created_task
    created_task >> end

কোড ব্যাখ্যা:

এখানে আমাদের একটি একক শুরুর কাজ এবং একটি একক শেষ টাস্ক (উভয় ডামি) রয়েছে।

তারপরে ফর লুপের সাথে প্রারম্ভিক কার্য থেকে আমরা একই পাইথনকে কলযোগ্য 10 টি কার্য তৈরি করি। কার্যগুলি ক্রিয়েটি_ডিনামিক_টাস্কে তৈরি করা হয়।

প্রতিটি অজগরকে কলযোগ্য আমরা সমান্তরাল কার্যের মোট সংখ্যা এবং বর্তমান টাস্ক সূচকে আর্গুমেন্ট হিসাবে পাস করি।

ধরুন আপনার কাছে বিস্তারিত জানার জন্য 1000 টি আইটেম রয়েছে: প্রথম টাস্কটি ইনপুটটিতে পাবেন যে এটি 10 ​​খণ্ডের মধ্যে প্রথম অংশটি বিশদভাবে বর্ণনা করা উচিত। এটি 1000 আইটেমগুলিকে 10 টি অংশে বিভক্ত করবে এবং প্রথমটি বিস্তারিতভাবে বর্ণনা করবে।


4
এটি একটি ভাল সমাধান, যতক্ষণ না আপনার প্রতি আইটেমের জন্য নির্দিষ্ট টাস্কের প্রয়োজন নেই (যেমন অগ্রগতি, ফলাফল, সাফল্য / ব্যর্থতা, পুনরায় চেষ্টা করা ইত্যাদি)
অ্যালোনজো

@ এনা parallelTaskসংজ্ঞায়িত নয়: আমি কি কিছু মিস করছি?
অ্যান্থনি কেনে

4
@ অ্যান্থনিকেন এটি অজগর ফাংশন যা আপনার আসলে কিছু করার জন্য বলা উচিত। কোডটিতে মন্তব্য হিসাবে এটি মোট উপাদানগুলির একটি অংশকে বিস্তারিতভাবে জানাতে মোট সংখ্যা এবং বর্তমান সংখ্যা হিসাবে ইনপুট হিসাবে গ্রহণ করবে।
এনা

4

আপনার মনে হয় আপনি যা অনুসন্ধান করছেন তা ডায়াগুলি তৈরি করছে যা কিছুদিন আগে এই ধরণের পরিস্থিতির মুখোমুখি হয়েছিলাম কিছু অনুসন্ধানের পরে আমি এই ব্লগটি পেয়েছি ।

ডায়নামিক টাস্ক জেনারেশন

start = DummyOperator(
    task_id='start',
    dag=dag
)

end = DummyOperator(
    task_id='end',
    dag=dag)

def createDynamicETL(task_id, callableFunction, args):
    task = PythonOperator(
        task_id = task_id,
        provide_context=True,
        #Eval is used since the callableFunction var is of type string
        #while the python_callable argument for PythonOperators only receives objects of type callable not strings.
        python_callable = eval(callableFunction),
        op_kwargs = args,
        xcom_push = True,
        dag = dag,
    )
    return task

ডিএজি ওয়ার্কফ্লো সেট করা হচ্ছে

with open('/usr/local/airflow/dags/config_files/dynamicDagConfigFile.yaml') as f:
    # Use safe_load instead to load the YAML file
    configFile = yaml.safe_load(f)

    # Extract table names and fields to be processed
    tables = configFile['tables']

    # In this loop tasks are created for each table defined in the YAML file
    for table in tables:
        for table, fieldName in table.items():
            # In our example, first step in the workflow for each table is to get SQL data from db.
            # Remember task id is provided in order to exchange data among tasks generated in dynamic way.
            get_sql_data_task = createDynamicETL('{}-getSQLData'.format(table),
                                                 'getSQLData',
                                                 {'host': 'host', 'user': 'user', 'port': 'port', 'password': 'pass',
                                                  'dbname': configFile['dbname']})

            # Second step is upload data to s3
            upload_to_s3_task = createDynamicETL('{}-uploadDataToS3'.format(table),
                                                 'uploadDataToS3',
                                                 {'previous_task_id': '{}-getSQLData'.format(table),
                                                  'bucket_name': configFile['bucket_name'],
                                                  'prefix': configFile['prefix']})

            # This is where the magic lies. The idea is that
            # once tasks are generated they should linked with the
            # dummy operators generated in the start and end tasks. 
            # Then you are done!
            start >> get_sql_data_task
            get_sql_data_task >> upload_to_s3_task
            upload_to_s3_task >> end

কোডটি একসাথে রাখার পরে আমাদের ডিএজি দেখতে কেমন লাগে এখানে চিত্র বর্ণনা লিখুন

import yaml
import airflow
from airflow import DAG
from datetime import datetime, timedelta, time
from airflow.operators.python_operator import PythonOperator
from airflow.operators.dummy_operator import DummyOperator

start = DummyOperator(
    task_id='start',
    dag=dag
)


def createDynamicETL(task_id, callableFunction, args):
    task = PythonOperator(
        task_id=task_id,
        provide_context=True,
        # Eval is used since the callableFunction var is of type string
        # while the python_callable argument for PythonOperators only receives objects of type callable not strings.
        python_callable=eval(callableFunction),
        op_kwargs=args,
        xcom_push=True,
        dag=dag,
    )
    return task


end = DummyOperator(
    task_id='end',
    dag=dag)

with open('/usr/local/airflow/dags/config_files/dynamicDagConfigFile.yaml') as f:
    # use safe_load instead to load the YAML file
    configFile = yaml.safe_load(f)

    # Extract table names and fields to be processed
    tables = configFile['tables']

    # In this loop tasks are created for each table defined in the YAML file
    for table in tables:
        for table, fieldName in table.items():
            # In our example, first step in the workflow for each table is to get SQL data from db.
            # Remember task id is provided in order to exchange data among tasks generated in dynamic way.
            get_sql_data_task = createDynamicETL('{}-getSQLData'.format(table),
                                                 'getSQLData',
                                                 {'host': 'host', 'user': 'user', 'port': 'port', 'password': 'pass',
                                                  'dbname': configFile['dbname']})

            # Second step is upload data to s3
            upload_to_s3_task = createDynamicETL('{}-uploadDataToS3'.format(table),
                                                 'uploadDataToS3',
                                                 {'previous_task_id': '{}-getSQLData'.format(table),
                                                  'bucket_name': configFile['bucket_name'],
                                                  'prefix': configFile['prefix']})

            # This is where the magic lies. The idea is that
            # once tasks are generated they should linked with the
            # dummy operators generated in the start and end tasks. 
            # Then you are done!
            start >> get_sql_data_task
            get_sql_data_task >> upload_to_s3_task
            upload_to_s3_task >> end

এটি সম্পূর্ণ আশা ছিল সম্পূর্ণ আশা এটি অন্য কাউকে সাহায্য করবে


আপনি নিজে থেকে এটি অর্জন করেছেন? আমি ক্লান্ত. তবে আমি ব্যর্থ হয়েছি।
নিউট

হ্যাঁ, এটি আমার পক্ষে কাজ করেছিল। আপনি কোন সমস্যার মুখোমুখি?
মুহাম্মদ বিন আলী

4
আমি বুঝতে পেরেছি. আমার সমস্যা সমাধান করা হয়েছে। ধন্যবাদ আমি ডকার ইমেজে পরিবেশের পরিবর্তনশীলগুলি পড়ার সঠিক উপায়টি পাইনি।
নিউট

4
যদি টেবিলের আইটেমগুলি পরিবর্তিত হতে পারে, সুতরাং আমরা সেগুলি স্থিতিশীল যমল ফাইলটিতে রাখতে পারি না?
ফ্রাঙ্কজু

আপনি এটি কোথায় ব্যবহার করছেন তা এটি নির্ভর করে। যদিও আপনার পরামর্শে আমি আগ্রহী হব। @ ফ্র্যাঙ্কজু কিভাবে এটি সঠিকভাবে করা উচিত?
মুহাম্মদ বিন আলী

3

আমি মনে করি https://github.com/mastak/airflow_m લ્ટ_dagrun এ আমি এর একটি সুন্দর সমাধান পেয়েছি , যা ট্রিগারড্যাগআরনের মতো একাধিক ড্যাগ্রুনগুলি ট্রিগার করে ড্যাগআরনগুলির সহজ এনকিউনিং ব্যবহার করে । বেশিরভাগ ক্রেডিট https://github.com/mastak এ যায় , যদিও আমাকে কিছু বিবরণ দিতে হয়েছিল এটি সাম্প্রতিকতম এয়ারফ্লোতে কাজ করার জন্য করতে হয়েছিল।

সমাধানটিতে একটি কাস্টম অপারেটর ব্যবহার করা হয় যা বেশ কয়েকটি ড্যাগআরুনগুলি ট্রিগার করে :

from airflow import settings
from airflow.models import DagBag
from airflow.operators.dagrun_operator import DagRunOrder, TriggerDagRunOperator
from airflow.utils.decorators import apply_defaults
from airflow.utils.state import State
from airflow.utils import timezone


class TriggerMultiDagRunOperator(TriggerDagRunOperator):
    CREATED_DAGRUN_KEY = 'created_dagrun_key'

    @apply_defaults
    def __init__(self, op_args=None, op_kwargs=None,
                 *args, **kwargs):
        super(TriggerMultiDagRunOperator, self).__init__(*args, **kwargs)
        self.op_args = op_args or []
        self.op_kwargs = op_kwargs or {}

    def execute(self, context):

        context.update(self.op_kwargs)
        session = settings.Session()
        created_dr_ids = []
        for dro in self.python_callable(*self.op_args, **context):
            if not dro:
                break
            if not isinstance(dro, DagRunOrder):
                dro = DagRunOrder(payload=dro)

            now = timezone.utcnow()
            if dro.run_id is None:
                dro.run_id = 'trig__' + now.isoformat()

            dbag = DagBag(settings.DAGS_FOLDER)
            trigger_dag = dbag.get_dag(self.trigger_dag_id)
            dr = trigger_dag.create_dagrun(
                run_id=dro.run_id,
                execution_date=now,
                state=State.RUNNING,
                conf=dro.payload,
                external_trigger=True,
            )
            created_dr_ids.append(dr.id)
            self.log.info("Created DagRun %s, %s", dr, now)

        if created_dr_ids:
            session.commit()
            context['ti'].xcom_push(self.CREATED_DAGRUN_KEY, created_dr_ids)
        else:
            self.log.info("No DagRun created")
        session.close()

তারপরে আপনি আপনার পাইথন অপারেটারে কলযোগ্য ফাংশন থেকে কয়েকটি ডাগরুন জমা দিতে পারেন, উদাহরণস্বরূপ:

from airflow.operators.dagrun_operator import DagRunOrder
from airflow.models import DAG
from airflow.operators import TriggerMultiDagRunOperator
from airflow.utils.dates import days_ago


def generate_dag_run(**kwargs):
    for i in range(10):
        order = DagRunOrder(payload={'my_variable': i})
        yield order

args = {
    'start_date': days_ago(1),
    'owner': 'airflow',
}

dag = DAG(
    dag_id='simple_trigger',
    max_active_runs=1,
    schedule_interval='@hourly',
    default_args=args,
)

gen_target_dag_run = TriggerMultiDagRunOperator(
    task_id='gen_target_dag_run',
    dag=dag,
    trigger_dag_id='common_target',
    python_callable=generate_dag_run
)

আমি কোডটি দিয়ে একটি কাঁটাচামড়াটি https://github.com/flinz/airflow_m લ્ટ_dagrun এ তৈরি করেছি


3

কাজের সময় গ্রাফ রান সময়ে উত্পন্ন হয় না। বরং গ্রাফটি তৈরি করা হয় যখন এটি আপনার ড্যাগস ফোল্ডার থেকে এয়ারফ্লো দ্বারা নেওয়া হয়। সুতরাং এটি প্রতিবার যখন কাজটি চালায় তখন আলাদা আলাদা গ্রাফ পাওয়া সম্ভব হয় না। আপনি লোড সময়ে একটি ক্যোয়ারির উপর ভিত্তি করে গ্রাফ তৈরি করতে একটি চাকরী কনফিগার করতে পারেন । সেই গ্রাফটি তার পরে প্রতিটি রানের জন্য একই থাকবে যা সম্ভবত খুব কার্যকর নয়।

আপনি একটি গ্রাফ ডিজাইন করতে পারেন যা একটি শাখা অপারেটর ব্যবহার করে ক্যোয়ারির ফলাফলের ভিত্তিতে প্রতিটি রানে বিভিন্ন কাজ সম্পাদন করে।

আমি যা করেছি তা হ'ল কার্যগুলির একটি সেট প্রাক কনফিগার করা এবং তারপরে ক্যোয়ারির ফলাফলগুলি গ্রহণ করা এবং সেগুলি কার্যগুলিতে বিতরণ করা। এটি সম্ভবত কোনওভাবেই ভাল কারণ আপনার কোয়েরিতে যদি প্রচুর ফলাফল ফিরে আসে তবে আপনি সম্ভবত কোনওভাবে সাম্প্রতিক কাজগুলি দিয়ে সময়সূচীটি বন্যা করতে চান না। এমনকি আরও নিরাপদ হওয়ার জন্য, আমি একটি পুল ব্যবহার করেছি তা নিশ্চিত করার জন্য যে আমার একচ্ছত্র অপ্রত্যাশিতভাবে বড় প্রশ্নের সাথে হাতছাড়া হবে না।

"""
 - This is an idea for how to invoke multiple tasks based on the query results
"""
import logging
from datetime import datetime

from airflow import DAG
from airflow.hooks.postgres_hook import PostgresHook
from airflow.operators.mysql_operator import MySqlOperator
from airflow.operators.python_operator import PythonOperator, BranchPythonOperator
from include.run_celery_task import runCeleryTask

########################################################################

default_args = {
    'owner': 'airflow',
    'catchup': False,
    'depends_on_past': False,
    'start_date': datetime(2019, 7, 2, 19, 50, 00),
    'email': ['rotten@stackoverflow'],
    'email_on_failure': True,
    'email_on_retry': False,
    'retries': 0,
    'max_active_runs': 1
}

dag = DAG('dynamic_tasks_example', default_args=default_args, schedule_interval=None)

totalBuckets = 5

get_orders_query = """
select 
    o.id,
    o.customer
from 
    orders o
where
    o.created_at >= current_timestamp at time zone 'UTC' - '2 days'::interval
    and
    o.is_test = false
    and
    o.is_processed = false
"""

###########################################################################################################

# Generate a set of tasks so we can parallelize the results
def createOrderProcessingTask(bucket_number):
    return PythonOperator( 
                           task_id=f'order_processing_task_{bucket_number}',
                           python_callable=runOrderProcessing,
                           pool='order_processing_pool',
                           op_kwargs={'task_bucket': f'order_processing_task_{bucket_number}'},
                           provide_context=True,
                           dag=dag
                          )


# Fetch the order arguments from xcom and doStuff() to them
def runOrderProcessing(task_bucket, **context):
    orderList = context['ti'].xcom_pull(task_ids='get_open_orders', key=task_bucket)

    if orderList is not None:
        for order in orderList:
            logging.info(f"Processing Order with Order ID {order[order_id]}, customer ID {order[customer_id]}")
            doStuff(**op_kwargs)


# Discover the orders we need to run and group them into buckets for processing
def getOpenOrders(**context):
    myDatabaseHook = PostgresHook(postgres_conn_id='my_database_conn_id')

    # initialize the task list buckets
    tasks = {}
    for task_number in range(0, totalBuckets):
        tasks[f'order_processing_task_{task_number}'] = []

    # populate the task list buckets
    # distribute them evenly across the set of buckets
    resultCounter = 0
    for record in myDatabaseHook.get_records(get_orders_query):

        resultCounter += 1
        bucket = (resultCounter % totalBuckets)

        tasks[f'order_processing_task_{bucket}'].append({'order_id': str(record[0]), 'customer_id': str(record[1])})

    # push the order lists into xcom
    for task in tasks:
        if len(tasks[task]) > 0:
            logging.info(f'Task {task} has {len(tasks[task])} orders.')
            context['ti'].xcom_push(key=task, value=tasks[task])
        else:
            # if we didn't have enough tasks for every bucket
            # don't bother running that task - remove it from the list
            logging.info(f"Task {task} doesn't have any orders.")
            del(tasks[task])

    return list(tasks.keys())

###################################################################################################


# this just makes sure that there aren't any dangling xcom values in the database from a crashed dag
clean_xcoms = MySqlOperator(
    task_id='clean_xcoms',
    mysql_conn_id='airflow_db',
    sql="delete from xcom where dag_id='{{ dag.dag_id }}'",
    dag=dag)


# Ideally we'd use BranchPythonOperator() here instead of PythonOperator so that if our
# query returns fewer results than we have buckets, we don't try to run them all.
# Unfortunately I couldn't get BranchPythonOperator to take a list of results like the
# documentation says it should (Airflow 1.10.2). So we call all the bucket tasks for now.
get_orders_task = PythonOperator(
                                 task_id='get_orders',
                                 python_callable=getOpenOrders,
                                 provide_context=True,
                                 dag=dag
                                )
get_orders_task.set_upstream(clean_xcoms)

# set up the parallel tasks -- these are configured at compile time, not at run time:
for bucketNumber in range(0, totalBuckets):
    taskBucket = createOrderProcessingTask(bucketNumber)
    taskBucket.set_upstream(get_orders_task)


###################################################################################################

মনে রাখবেন যে কোনও কাজের ফলস্বরূপ উড়ানে সাবড্যাগগুলি তৈরি করা সম্ভব হতে পারে বলে মনে হয় তবে যাইহোক, সাবড্যাগগুলিতে বেশিরভাগ ডকুমেন্টেশন সেই বৈশিষ্ট্যটি থেকে দূরে থাকার পরামর্শ দিয়েছি কারণ এটি সমাধানের চেয়ে আরও সমস্যার সৃষ্টি করে অধিকাংশ ক্ষেত্রে. আমি পরামর্শগুলি দেখেছি যে সাবড্যাগগুলি বিল্ট-ইন বৈশিষ্ট্য হিসাবে শীঘ্রই সরিয়ে দেওয়া হতে পারে।
পঁচা

এছাড়াও মনে রাখবেন যে for tasks in tasksআমার উদাহরণের লুপে আমি যে বস্তুটি পুনরাবৃত্তি করছি তা মুছে ফেলছি। এটি একটি খারাপ ধারণা। পরিবর্তে কীগুলির একটি তালিকা পান এবং তার উপর পুনরাবৃত্তি করুন - বা মুছুনগুলি এড়িয়ে যান। একইভাবে, xcom_pull যদি কোনও তালিকা না দেয় (তালিকা বা খালি তালিকার পরিবর্তে) লুপের জন্যও ব্যর্থ হয়। কেউ 'ফর' এর আগে এক্সকম_পুল চালাতে চাইতে পারেন এবং তারপরে এটি কোনওটি নয় কিনা তা যাচাই করতে পারেন - বা নিশ্চিত করুন যে সেখানে কমপক্ষে একটি খালি তালিকা রয়েছে। ওয়াইএমএমভি শুভকামনা!
পঁচা

4
কি আছে open_order_task?
alltej

আপনি ঠিক বলেছেন, এটি আমার উদাহরণের একটি টাইপো। এটি get_orders_task.set_upstream () হওয়া উচিত। আমি এটা ঠিক করব.
পচা

0

বুঝতে পারছিনা সমস্যা কি?

এখানে একটি আদর্শ উদাহরণ। এখন যদি ফাংশন সাবড্যাগের সাথে প্রতিস্থাপন for i in range(5):করে for i in range(random.randint(0, 10)):তবে সবকিছু কাজ করবে। এখন কল্পনা করুন যে অপারেটর 'স্টার্ট' কোনও ফাইলটিতে ডেটা রাখে এবং এলোমেলো মানের পরিবর্তে ফাংশনটি এই ডেটাটি পড়বে। তারপরে অপারেটর 'স্টার্ট' কাজের সংখ্যাকে প্রভাবিত করবে।

সমস্যাটি কেবলমাত্র ইউআই-তে প্রদর্শিত হবে কারণ যেহেতু সাবড্যাগে প্রবেশ করার সময়, এই মুহুর্তে ফাইল / ডাটাবেস / এক্সকম থেকে শেষ সংখ্যাটি পড়ার মতো কাজগুলির সংখ্যা সমান হবে। যা স্বয়ংক্রিয়ভাবে একবারে একটি ডাগের কয়েকটি লঞ্চের উপর বিধিনিষেধ দেয়।


-1

আমি এই মিডিয়াম পোস্টটি পেয়েছি যা এই প্রশ্নের সাথে খুব মিল। তবে এটি টাইপস পূর্ণ, এবং আমি যখন এটি প্রয়োগ করার চেষ্টা করেছি তখন কাজ করে না।

উপরের আমার উত্তরটি নিম্নরূপ:

আপনি যদি গতিশীল কাজগুলি তৈরি করে থাকেন তবে অবশ্যই আপনাকে অবশ্যই এমন কিছু নিয়ে পুনরাবৃত্তি করতে হবে যা একটি প্রবাহের কাজ দ্বারা তৈরি করা হয়নি, বা সেই কার্যটি থেকে স্বাধীনভাবে সংজ্ঞায়িত করা যেতে পারে। আমি শিখেছি যে আপনি মৃত্যুর তারিখ বা অন্যান্য বায়ু প্রবাহের ভেরিয়েবলগুলি কোনও টেমপ্লেটের বাইরের কিছুতে (উদাহরণস্বরূপ, একটি টাস্ক) পাস করতে পারবেন না যেমন অন্য অনেকে আগে উল্লেখ করেছেন। আরও দেখুন এই পোস্টে


আপনি যদি আমার মন্তব্যটি একবার দেখুন, আপনি দেখতে পাবেন যে প্রবাহের কাজগুলির ফলাফলের উপর ভিত্তি করে কার্যগুলি তৈরি করা সম্ভব।
ক্রিস্টোফার বেক
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.