1293.66 블랙보드 데이터의 Tick 간 일관성
1. Tick 간 일관성의 정의
블랙보드 데이터의 Tick 간 일관성(inter-tick consistency)이란, 연속된 Tick에서 블랙보드의 데이터가 논리적으로 유효하고 상호 모순 없는 상태를 유지하는 속성을 의미한다. 블랙보드는 Tick 간에 데이터를 유지하는 영속적 저장소이므로, 한 Tick에서 기록된 데이터가 후속 Tick에서 읽힐 때 의미적으로 정합적이어야 한다(Faconti, 2022).
2. Tick 간 데이터 유지의 특성
블랙보드의 데이터는 명시적으로 덮어쓰거나 삭제하지 않는 한, Tick 간에 영속적으로 유지된다. 이는 블랙보드가 Tick 주기와 독립적인 수명을 가지기 때문이다.
Tick N: setOutput("goal_pose", P1) → 블랙보드["goal_pose"] = P1
Tick N+1: (쓰기 없음) → 블랙보드["goal_pose"] = P1 (유지)
Tick N+2: setOutput("goal_pose", P2) → 블랙보드["goal_pose"] = P2 (갱신)
Tick N+3: (쓰기 없음) → 블랙보드["goal_pose"] = P2 (유지)
3. 일관성 위협 요인
3.1 부분 갱신(Partial Update)
복수의 관련 블랙보드 키가 독립적으로 갱신되는 경우, 일부 키만 갱신되고 나머지는 이전 Tick의 값을 유지하는 부분 갱신이 발생할 수 있다.
Tick N:
목표 위치 갱신: setOutput("goal_x", 5.0), setOutput("goal_y", 3.0)
Tick N+1:
새 목표 수신, x만 갱신됨 (y 갱신 노드가 FAILURE로 건너뜀):
setOutput("goal_x", 8.0)
→ goal_x = 8.0, goal_y = 3.0 ← 불일치: 서로 다른 시점의 좌표
이 상황에서 goal_x는 Tick N+1의 값이고 goal_y는 Tick N의 값이므로, 좌표 쌍이 유효하지 않은 위치를 가리킬 수 있다.
3.2 외부 갱신의 비동기성
콜백에 의한 블랙보드 갱신이 Tick 실행 중간에 발생하면, 동일 Tick 내에서 갱신 전후의 값이 혼재할 수 있다. 이는 멀티스레드 실행기 환경에서 발생 가능하다.
Tick 실행 중:
Node_A: getInput("pose") → P1 (갱신 전)
─── 콜백 스레드에서 블랙보드 갱신: pose = P2 ───
Node_B: getInput("pose") → P2 (갱신 후)
→ Node_A와 Node_B가 다른 pose 값을 기반으로 동작
3.3 잔류 데이터(Stale Data)
이전 실행 경로에서 기록된 블랙보드 데이터가 현재 실행 경로에서 의미 없는 값으로 잔류하는 경우이다.
Tick N (경로 A 실행):
DetectObject → setOutput("object_id", 42)
Tick N+1 (경로 B 실행, DetectObject 미실행):
GraspObject → getInput("object_id") → 42 ← 이전 경로의 잔류 데이터
경로 B에서는 새로운 물체 감지가 수행되지 않았으나, 이전 경로에서 기록된 object_id가 잔류하여 잘못된 물체를 파지할 수 있다.
4. 일관성 확보 전략
4.1 관련 데이터의 원자적 갱신
의미적으로 연관된 복수의 블랙보드 키를 항상 함께 갱신하여 부분 갱신을 방지한다.
class UpdateGoal : public BT::SyncActionNode {
BT::NodeStatus tick() override {
geometry_msgs::msg::Pose goal;
getInput("new_goal", goal);
// 관련 데이터를 단일 노드에서 원자적으로 갱신
setOutput("goal_x", goal.position.x);
setOutput("goal_y", goal.position.y);
setOutput("goal_theta", getYaw(goal.orientation));
setOutput("goal_timestamp", now());
return BT::NodeStatus::SUCCESS;
}
};
또는 복합 데이터 타입을 사용하여 단일 키로 관련 정보를 묶는다.
// 복합 타입으로 원자적 갱신
setOutput("goal_pose", goal); // geometry_msgs::msg::Pose 전체를 하나의 키로
4.2 타임스탬프 기반 신선도 확인
블랙보드 데이터에 타임스탬프를 함께 기록하여, 읽기 시 데이터의 신선도를 확인한다.
BT::NodeStatus tick() override {
double timestamp;
getInput("data_timestamp", timestamp);
double current_time = now();
if (current_time - timestamp > max_age_) {
// 데이터 만료: 잔류 데이터 사용 방지
return BT::NodeStatus::FAILURE;
}
// 유효한 데이터 사용
return processData();
}
4.3 Tick 시작 시 데이터 스냅샷
Tick 실행 전에 외부 소스의 데이터를 블랙보드에 일괄 기록하고, Tick 실행 중에는 블랙보드의 스냅샷만 사용하는 패턴이다.
void tickLoop() {
while (rclcpp::ok()) {
executor_.spin_some(); // 콜백 처리
// Tick 전 데이터 스냅샷 기록
updateBlackboardFromCallbacks();
tree_.tickOnce(); // Tick 실행 (스냅샷 데이터 사용)
rate.sleep();
}
}
void updateBlackboardFromCallbacks() {
// 콜백에서 수집된 최신 데이터를 블랙보드에 일괄 기록
blackboard_->set("robot_pose", latest_pose_);
blackboard_->set("battery_level", latest_battery_);
blackboard_->set("scan_data", latest_scan_);
}
이 패턴에서 Tick 실행 중에는 블랙보드 데이터가 외부에 의해 변경되지 않으므로, 단일 Tick 내의 일관성이 보장된다.
4.4 블랙보드 키 초기화
트리의 실행 경로가 변경될 때, 이전 경로에서 기록된 잔류 데이터를 명시적으로 초기화하여 잘못된 참조를 방지한다.
<Sequence>
<ClearBlackboardKey key="object_id"/>
<DetectObject output="{object_id}"/>
<GraspObject input="{object_id}"/>
</Sequence>
5. Tick 간 일관성과 Reactive 노드의 관계
ReactiveSequence에서 조건이 매 Tick마다 재평가되는 경우, 블랙보드 값의 변화가 즉시 조건 판정에 반영된다. 이 경우 Tick 간 일관성은 조건 재평가의 정확성을 보장하는 데 중요하다.
Tick N: battery=0.3 → IsBatteryAbove(0.2)=SUCCESS → Action(RUNNING)
Tick N+1: battery=0.15 → IsBatteryAbove(0.2)=FAILURE → Action에 Halt
배터리 값이 Tick 간에 갱신되면, 조건 재평가 결과도 변경된다. 블랙보드의 배터리 값이 정확한 시점의 값을 반영하면 일관성이 유지된다.
6. 일관성 수준의 정리
| 수준 | 보장 범위 | 확보 방법 |
|---|---|---|
| Tick 내 일관성 | 단일 Tick 내 모든 노드가 동일 데이터 참조 | 단일 스레드 모델 + 스냅샷 |
| Tick 간 일관성 | 연속 Tick에서 데이터의 논리적 정합성 | 원자적 갱신 + 타임스탬프 |
| 키 간 일관성 | 관련 키들의 동시 유효성 | 복합 타입 또는 동시 갱신 |
| 경로 간 일관성 | 실행 경로 변경 시 잔류 데이터 부재 | 키 초기화 |
참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- Faconti, D. (2022). BehaviorTree.CPP documentation and API reference. https://www.behaviortree.dev/