반디북
데이터 지향 프로그래밍
Ch5
우석
만혁

기본 동시성 제어 - 가정 불화

시스템이 동시성을 관리하도록 바꿔야 하는 부분은 반영 단계(commit phase) 뿐이다.

반영 단계는 불일치 조정 알고리즘을 포함하는데, 데이터가 불변 해시맵으로 표현된다면

이 알고리즘은 범용적으로 사용될 수 있다.

DOP 에서는 반영 단계의 코드만 상태를 다루기 때문에 잠금(lock)이 전혀 필요 없는

낙관적 동시성 제어 전략을 사용할 수 있다.

따라서 읽고 쓰는 성능이 매우 높아진다.

동시에 변경할 때 발생하는 불일치를 조정(reconciliation) 하는 알고리즘을 구현해야 하기 때문에

간단하지는 않다.

낙관적 동시성 제어는 불변 데이터와 효율이 좋다

5.3 컬렉션 축소(reduce)

// 5.1 _.reduce를 사용한 합계 계산
_.reduce([1,2,3], (res, elem) => {
    return res + elem;
}, 0);
// -> 6
 
 
 
function reduce(coll, f, initVal) {
    let currentres = initVal;
    for (let i = 0; i < coll.length; i++) {
        currentres = f(currentres, coll[i]);
    }
    return currentres;
}

5.4 구조적 비교

구조적 공유 방식에서는 두 버전의 시스템 상태가 중첩된 상태 대부분을 공유한다.

따라서 대부분의 경우 diffObjects 가 호출되면 data1, data2 가 동일해서 바로 반환된다.

function diffObjects(data1, data2) {
    const emptyObject = _.isArray(data) ? [] : {};
    
    if (data1 == data2) {
        return emptyObject;
    }
    
    const keys = _.union(_.keys(data1), _.keys(data2));
    
    return _.reduce(keys, (acc, key) => {
        const res = diff(_.get(data1, key), _.get(data2, key));
 
        if((_.isObject(res) && _.isEmpty(res)) || res == 'no-diff') {
            return acc;
        }
        return _.set(acc, [key], res);
    },
    emptyObject);
}
 
function diff(data1, data2) {
    if(_.isObject(data1) && _.isObject(data2)) { 
        return diffObjects(data1, data2);
    }
    if(data1 !== data2) { 
        return data2;
    }
    return "no-diff";
}
 

상충되지 않는 변경 예시

 
const library = { 
  "catalog": { 
  "booksByIsbn": { 
    "978-1779501127": {
      "isbn": "978-1779501127",
      "title": "Watchmen",
      "publicationYear": 1987, 
      "authorlds": ["alan-moore", "dave-gibbons"] 
    }
  }, 
    "authorsById": { 
      "alan-moore": {
        "name": "Alan Moore",
        "booklsbns": ["978-1779501127"] 
      }, 
      "dave-gibbons": {
        "name": "Dave Gibbons",
        "booklsbns": ["978-1779501127"]
      }
    }
  } 
}
 
const previous = library;
const next = _.set(
  library,
  ["catalog", "booksByIsbn", "978-1779501127","publicationYear"],
  1986);
 
const libraryWithUpdatedTitle = _.set( 
  library, 
  ["catalog", "booksByIsbn", "978-1779501127", "title"], 
  "The Watchmen");
 
 
 
const current = _.set( 
  libraryWithUpdatedTitle, 
  ["catalog", "authorsById", "dave-gibbons", "name"], 
  "David Chester Gibbons")
// 5.10 중첩된 맵의 정보 경로 계산
function informationpaths (obj, path = []) { 
    return _.reduce(obj,(acc, v, k) => {
        if (_.isObject(v)) { 
            return _.concat(
                acc, 
                informationpaths(
                    v, 
                    _.concat(path, k))
            );
        }  
    return _.concat(acc, [_.concat(path, k)]);
    }, 
[]);
}
// 5.13 두 맵의 비교 결과에 공통 정보 경로가 있는지 확인
function havePathInCommon(diffl, diff2) {
    return 
        !_.isEmpty(_.intersection(informationpaths(diff1), 
        informationpaths(diff2)));
}

두 변경간에 불일치가 없다면 이전 버전에서 차기 버전으로 바꾸는 변경사항을

현재 버전에 안전하게 적용할 수 있다.

// 5.15 systemstate class
class SystemState {
    systemData; 
 
    get() {
        return this.systemData;
    }
 
    set(_systemData) {
        this.systemData = _systemData;
    }
 
    commit(previous, next) {
        const nextSystemData = SystemConsistency.reconcile(
            this.systemData,
            previous,
            next
        );
        if(!Consistency.validate(previous, nextSystemDatat)) {
            throw "유효하지 않은 시스템 데이터로 변경하려고 했음";
        }
 
        this.systemData = next;
    }
}
class SystemConsistency {
  static threeWayMerge(current, previous, next) {
    var previousToCurrent = diff(previous, current); 
    var previousToNext = diff(previous, next);
 
    if (!havePathInCommon(previousToCurrent, previousToNext)) {
      return _merge(current, previousToNext);  // 병합 수행
    }
 
    throw new Error("Conflicting concurrent mutations.");
  }
 
  static reconcile(current, previous, next) {
    if (current === previous) {
      return next;
    }
 
    return SystemConsistency.threeWayMerge(current, previous, next);
  }
}

이전 버전과 현재 버전의 참조를 비교하는 이유는

불변 데이터는 참조를 비교해도 안전하다.