Phonograph에 쓰는 기존 Slate 앱
Object Storage v1인 Phonograph 쓰기는 Legacy 기능이며 새 업무 흐름에는 지원하지 않습니다. 새 구성은 Object Storage v2 이전과 Ontology Action을 검토합니다.
세 데이터 구성 요소
| 구성 요소 | 역할 |
|---|---|
| Source dataset | 편집의 기준 데이터셋입니다. 행을 고유하게 식별하는 기본 키가 있어야 합니다. |
| Phonograph sync | 사용자 편집을 보관하는 수동 편집 캐시입니다. Postgres 동기화와 구분합니다. |
| Writeback dataset | 기준 데이터와 사용자 편집을 반영하여 Foundry에 저장할 출력입니다. 원천 데이터셋 자체는 변경하지 않습니다. |
예제는 소행성 이름과 사용자 입력용 research_notes 열을 가진 가상 데이터셋 asteroid_notes를 사용합니다.
원천과 동기화 설정
기존 Dataset 탭의 + Add로 원천을 추가하고 Phonograph 동기화를 설정합니다. 이 쓰기 색인은 struct 열을 지원하지 않습니다. 해당 Legacy 경로의 DateTime 열은 색인에 실패하므로 Contour, SQL 또는 Python transform에서 Timestamp로 변환할 수 있습니다.
기본 키는 한 행을 고유하게 식별해야 합니다. 필요하면 transform에서 여러 열을 합쳐 키를 만듭니다. 복합 기본 키도 정의할 수 있지만 편집 요청에 여러 키값을 넣어야 하므로 구성이 복잡해집니다.
Browse로 편집을 저장할 출력 데이터셋의 위치와 이름을 지정합니다.
동기화 등록 해제
동기화 설정 화면에서는 등록을 해제할 수 없습니다. Slate 질의에서 Table Registry - Unregister 엔드포인트에 Table RID를 전달하는 경로가 있습니다.
이 호출은 테이블의 모든 데이터를 영구적으로 삭제합니다. 입력 및 출력 데이터셋 자체를 삭제하지는 않으며 동기화 화면에서는 등록된 쓰기 동기화가 사라집니다. 이 질의를 남겨야 한다면 반드시 수동 실행으로 설정하고, 필요가 끝났다면 제거하여 페이지 로드나 값 변경으로 실행되지 않게 합니다.
행 조회
기본 흐름은 행 조회, 사용자 편집, 명시적인 제출, 결과 재조회입니다. 새 조회에는 폐기 예정인 Get All Rows 대신 Table Search Service의 Search를 사용합니다.
- 새 질의의 Source를 Phonograph2로 선택합니다.
- Available Services에서 Table Search Service를 고릅니다.
- 실제 테이블 RID와 필요한 필터 및 정렬을 Search Request에 넣습니다.
f_getTableId는 동기화 화면에서 확인한 단일 Table RID를 반환하는 함수로 구성합니다. 아래 예는 모든 행을 대상으로 하고 name으로 내림차순 정렬합니다.
{
"tableRids": ["{{f_getTableId}}"],
"filter": {"type":"matchAll","matchAll":{}},
"sort": {"name":{"order":"desc"}}
}
정렬은 선택 사항입니다. pagingToken과 pageSize를 표의 서버 측 페이지 조회 설정에 연결할 수도 있습니다. 표 이름을 w_rowsTable로 지정하면 다음 입력 참조를 사용할 수 있습니다.
| 입력 | 참조 |
|---|---|
| pagingToken | "{{w_rowsTable.gridOptions.pagingOptions.currentOffset}}" |
| pageSize | "{{w_rowsTable.gridOptions.pagingOptions.pageSize}}" |
기본 키로 조회
Get Rows는 기본 키 객체들의 목록으로 행을 가져옵니다. 사용자가 입력한 키가 이미 존재하는지 확인하여 추가 또는 편집 요청을 선택할 때에도 사용할 수 있습니다.
표에 표시할 열 배열
조회한 행은 primaryKey와 columns 객체를 갖습니다. Search 결과는 q_getAllRows.result.[0].hits의 각 항목에서 row를 읽습니다. 이전 Get All Rows의 results.rows 경로를 사용하지 않습니다.
Slate 표와 차트는 병렬 열 배열을 사용합니다. 일부 null 속성은 색인 결과에서 키가 빠질 수 있으므로 첫 행의 키만으로 모든 열을 정하지 않고 전체 행에서 열 이름을 모읍니다.
다음은 f_getAllRowsFormat 함수 본문입니다. row_number는 현재 조회 페이지의 행을 선택할 0부터 시작하는 보조 키입니다. 입력에 같은 이름의 열이 있다면 아래 보조 열과 표의 selectionIdentityColumnId에 다른 이름을 사용합니다.
const queryResult = {{q_getAllRows.result.[0].hits}}.map(
entry => Object.assign(Object.create(null), entry.row.primaryKey, entry.row.columns)
);
const columns = [...new Set(_.flatMap(queryResult, row => Object.keys(row)))];
const result = Object.assign(Object.create(null), ...columns.map(column => ({
[column]: queryResult.map(row => row[column])
})));
result.row_number = queryResult.map((row, index) => index);
return result;
행에서 빠진 속성값은 undefined입니다. 빈 조회는 빈 row_number 배열을 만듭니다. 이 예제는 Slate의 Lodash와 ES6 문법을 사용하므로 대상 브라우저의 지원 조건을 맞춥니다.
편집 요청
Table Storage Service의 Post Event는 rowAdded, rowModified와 rowDeleted를 받습니다. tableEditedEventPostRequest에는 primaryKey와 payload를 넣습니다. payload의 type과 같은 이름의 객체를 사용합니다.
다음은 가상 소행성 행의 메모를 수정하는 요청입니다. 복합 기본 키라면 primaryKey에 구성한 키 열을 모두 넣습니다.
{
"primaryKey": {"name":"Hebe"},
"payload": {
"type":"rowModified",
"rowModified": {"columns":{"research_notes":"관찰 메모"}}
}
}
선택한 행의 메모 수정
w_rowsTable에 f_getAllRowsFormat 결과를 표시하고 selectionIdentityColumnId를 row_number로 설정합니다. 현재 조회 결과의 행 하나를 선택한 상태에서 w_notesInput에 수정할 메모를 입력합니다.
f_createTableEditEvent 본문은 다음과 같습니다.
var hits = {{q_getAllRows.result.[0].hits}};
var selection = {{w_rowsTable.selectedRowKeys}};
if (selection.length !== 1 || hits[selection[0]] === undefined) return null;
var selectedRow = hits[selection[0]].row;
var updatedCols = Object.assign(Object.create(null), selectedRow.columns);
updatedCols.research_notes = {{w_notesInput.text}};
return {
primaryKey: { name: selectedRow.primaryKey.name },
payload: {
type: "rowModified",
rowModified: { columns: updatedCols }
}
};
이 예제의 선택 키는 현재 조회 페이지의 행 번호입니다. 조회 페이지가 바뀌면 새 결과에서 행을 다시 선택합니다. 함수 결과를 updateRow 질의의 tableEditedEventPostRequest 매개변수에 연결합니다.
명시적인 제출
Run 옆 드롭다운에서 Run manually를 선택합니다. 그러면 페이지 재로드나 의존값 변경만으로 편집 질의가 실행되지 않습니다. 버튼 클릭 이벤트를 updateRow.run에 연결합니다.
단일 선택이 없어 요청이 null인 경우에는 이벤트에서 제출을 중단합니다.
if ({{f_createTableEditEvent}} === null) return {{slDisableAction}};
폼에는 업무 규칙에 맞는 검증을 둘 수 있습니다. f_validateForm 같은 검증 함수의 출력 계약을 다음처럼 구성하여 버튼 비활성화와 메시지에 연결합니다.
| 출력 필드 | 역할 |
|---|---|
| disableSubmit | 제출을 막을지 나타내는 Boolean입니다. |
| globalMessage | 폼 전체의 안내 문자열입니다. |
| fields의 각 입력 항목 | 해당 위젯의 value와 message를 가진 객체입니다. |
버튼의 비활성화 설정에 {{f_validateForm.disableSubmit}}을 연결하고 입력 옆에 해당 메시지를 표시합니다. 실제 검증 규칙은 해당 폼의 업무 조건으로 구현합니다.
행 추가와 삭제
행 추가는 payload.type과 객체 이름을 rowAdded로 바꾸고 columns를 전달합니다. 이미 있는 기본 키로 추가하면 실패합니다. 오류를 Toast로 알리거나 q_validatePrimaryKey가 Get Rows로 존재 여부를 확인하게 할 수 있습니다. 확인 결과에 따라 f_createTableEditEvent가 rowModified 또는 rowAdded를 만듭니다.
행 삭제는 키와 빈 rowDeleted 객체만 필요합니다.
{
"primaryKey": {"name":"Hebe"},
"payload": {"type":"rowDeleted","rowDeleted":{}}
}
Search 필터
값으로 행을 찾을 때에는 Table Search Service의 Search를 사용합니다. 결과의 hits에서 row를 꺼내 앞의 변환 함수를 사용할 수 있습니다.
텍스트는 기본 색인 분석기에 의해 구분 문자 기준으로 토큰화됩니다. 여러 단어를 구문으로 찾으려면 queryString에 큰따옴표를 포함합니다. 예를 들어 JSON 문자열 "\"8156-Apron 2 P.2\""로 구문을 지정합니다.
terms
용어 배열을 받고 field를 생략하면 전체 열, 지정하면 해당 열을 대상으로 합니다.
{
"tableRids": ["{{f_getTableId}}"],
"filter": {
"type":"terms",
"terms":{"field":"first_name","terms":["agnese","ailee","woodman"]}
}
}
queryString
AND와 OR를 사용할 수 있고 여러 단어를 큰따옴표로 묶을 수 있습니다.
{
"tableRids": ["{{f_getTableId}}"],
"filter": {"type":"queryString","queryString":{"queryString":"jang"}}
}
range
숫자와 날짜 열에서 gt, gte, lt와 lte를 지원합니다.
{
"tableRids": ["{{f_getTableId}}"],
"filter": {
"type":"range",
"range":{"field":"start_date","lt":"2018-01-01","format":"yyyy-MM-dd"}
}
}
지리와 복합 필터
파이프라인에서 geohash를 만든 데이터에는 GeoBoundingBox와 GeoDistanceSearch로 경계 상자 또는 반경 검색을 구성할 수 있습니다. 이 Legacy 검색의 구체적인 요청 예는 Palantir 담당자에게 확인합니다.
필터는 and와 or로 중첩할 수 있습니다. 복잡한 요청은 함수에서 만들고 searchRequest 매개변수에 연결할 수 있습니다.
{
"tableRids": ["{{f_getTableId}}"],
"filter": {
"type":"and",
"and":[
{"type":"queryString","queryString":{"queryString":"pixoboo"}},
{"type":"terms","terms":{"field":"first_name","terms":["harcourt"]}}
]
}
}
검색 색인의 지연
Post Event가 성공해도 Search Service의 색인은 비동기로 갱신되므로 재조회에 이전값이 보일 수 있습니다. 예제 흐름은 다음과 같습니다.
- 쓰기 질의의
success이벤트에서 성공 알림을 표시하는 Toast를 엽니다. - Toast를 5,000ms 뒤 닫도록 구성합니다. 다른 구성에서는 3,000~4,000ms를 출발점으로 사용할 수도 있습니다.
- Toast의
didClose이벤트에q_getAllRows.run을 연결합니다. 이 이벤트는 Toast 위젯이 완전히 닫힌 뒤 발생합니다.
이 시간은 예제 대기값이며 색인 완료를 보장하는 시간이 아닙니다. 상황에 맞게 조정합니다. 기본 검색 정렬은 행의 마지막 수정 시각이므로 편집한 행이 뒤로 이동할 수 있습니다. 원하는 sort를 명시합니다.
Table Storage Service의 기본 키 조회는 반영된 편집을 포함하는 계약입니다. 따라서 쓰기 성공 뒤 q_getRowByPrimaryKey.run처럼 해당 행을 조회할 수 있습니다. Search의 비동기 색인과 이 저장 서비스의 읽기를 구분합니다.
Foundry 출력에 반영
updateRow는 우선 Phonograph 캐시를 바꿉니다. 이 Legacy writeback 출력 데이터셋의 Preview에서 Build를 실행해야 Foundry 출력에 나타납니다.
빌드 뒤 예제의 asteroid_notes_edited에서 research_notes 편집을 볼 수 있습니다. 원천 asteroid_notes를 직접 수정하는 방식은 아닙니다.
여러 편집을 한 요청으로 보내기
Table Service의 Post Events for Table은 tableEditedEventPostRequest 배열을 받습니다. rowModified, rowAdded와 rowDeleted를 섞을 수 있지만 같은 기본 키는 배열에 한 번만 나타나야 합니다. 같은 행의 수정과 삭제를 한 배열에 동시에 넣을 수 없습니다.
[
{
"primaryKey":{"name":"Hebe"},
"payload":{
"type":"rowModified",
"rowModified":{"columns":{"research_notes":"관찰 메모"}}
}
},
{
"primaryKey":{"name":"Iris"},
"payload":{"type":"rowDeleted","rowDeleted":{}}
}
]
장시간 사용하는 복잡한 앱은 초기 조회값을 프런트엔드 상태 변수에 넣고 편집을 그 변수에 먼저 반영하는 방식을 사용할 수 있습니다. 사용자가 저장할 때 기준 조회와의 차이로 일괄 요청을 만들고, 미저장 변경 수와 저장 버튼 또는 Toast로 상태를 알립니다. 이 방식에서는 각 편집마다 자동으로 전체 결과를 다시 읽지 않습니다. 별도 상태 관리가 필요하므로 복잡한 흐름에 도입할 때에는 Palantir 담당자와 설계를 검토합니다.
집계
이 Legacy Search는 Elasticsearch 2.0 집계 문법 중 value_count, max, geohash_grid, terms, top_hits, sum, cardinality, avg, nested, filter, histogram과 min을 지원합니다. 일부 집계는 근사값이 될 수 있으므로 사용하는 집계의 정확도 조건을 확인합니다.
문자열 visits로 묶으려면 Exact Match를 켜고 visits.raw 필드를 사용합니다.
{
"tableRids": ["{{f_getTableId}}"],
"filter": {"type":"matchAll","matchAll":{}},
"aggregations": {"visits":{"terms":{"field":"visits.raw"}}}
}
filter에 맞는 행을 visits 값으로 묶어 건수를 계산합니다. 집계만 필요하면 pageSize를 0으로 설정하여 개별 행 hits를 반환하지 않게 할 수 있습니다. 이는 상세 행의 페이지 크기이며 집계 버킷 수와는 다른 설정입니다.
다음은 집계 응답 형식의 예입니다.
{
"aggregations": {
"visits": {
"doc_count_error_upper_bound":0,
"sum_other_doc_count":30,
"buckets":[
{"key":"daily","doc_count":148},
{"key":"often","doc_count":141},
{"key":"weekly","doc_count":129}
]
}
}
}
집계 종류마다 반환 형태가 다르므로 필요하면 행과 열 변환을 사용해 차트나 표 입력에 맞춥니다.