-
Notifications
You must be signed in to change notification settings - Fork 0
/
persistence.tex
240 lines (189 loc) Β· 7.19 KB
/
persistence.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
\section{KVS: Abstract Erlang Database}
KVS is an Erlang abstraction over various native Erlang key-value databases,
like Mnesia. Its meta-schema includes only concept
of iterators (persisted linked lists) that are locked or guarded by
containers (list head pointers). All write operations to the list are
serialized using a single Erlang process to provide sequential consistency. The application
which starts Erlang processes per container called \footahref{https://github.com/synrc/feeds}{feeds}.
\paragraph{}
The best use-case for KVS and key-value storages is to store operational data.
This data should be later fed to SQL data warehouses for analysis. Operational data
stores should be scalable, secure, fault-tolerant and available. That is why
we store work-in-progress data in key-value storages.
\paragraph{}
KVS also supports queries that require secondary indexes, which
are not supported by all backends.
Currently KVS includes following storage backends: Mnesia, Riak and \footahref{https://github.com/synrc/kai}{KAI}.
\subsection{Polymorphic Records}
Any data in KVS is represented by regular Erlang records.
The first element of the tuple as usual indicates the name of bucket.
The second element usually corresponds to the index key field.
\begin{lstlisting}
Rec = {user,"maxim@synrc.com",[]}.
RecordName = element(1, Rec).
Id = element(2, Rec).
\end{lstlisting}
\newpage
\subsection{Iterators}
Iterator is a sequence of fields used as interface for all tables
represented as doubly-linked lists. It defines id, next, prev,
feed\_id fields. This fields should be at the beginning of user's record,
because KVS core is accessing relative position of the
field (like \#iterator.next) with setelement/element BIF, e.g.
\begin{lstlisting}
setelement(#iterator.next, Record, NewValue).
\end{lstlisting}
All records could be chained into the double-linked lists in the database.
So you can inherit from the ITERATOR record just like that:
\begin{lstlisting}
-record(access, {?ITERATOR(acl),
entry_id,
acl_id,
accessor,
action}).
\end{lstlisting}
\begin{lstlisting}
#iterator { record_name,
id,
version,
container,
feed_id,
prev,
next,
feeds,
guard }
\end{lstlisting}
This means your table will support add/remove linked list operations to lists.
\begin{lstlisting}
1> kvs:add(#user{id="mes@ua.fm"}).
2> kvs:add(#user{id="dox@ua.fm"}).
\end{lstlisting}
Read the chain (undefined means all)
\begin{lstlisting}
3> kvs:entries(kvs:get(feed, user), user, undefined).
[#user{id="mes@ua.fm"},#user{id="dox@ua.fm"}]
\end{lstlisting}
or just
\begin{lstlisting}
4> kvs:entries(user).
[#user{id="mes@ua.fm"},#user{id="dox@ua.fm"}]
\end{lstlisting}
Read flat values by all keys from table:
\begin{lstlisting}
4> kvs:all(user).
[#user{id="mes@ua.fm"},#user{id="dox@ua.fm"}]
\end{lstlisting}
\subsection{Containers}
If you are using iterators records this automatically
means you are using containers. Containers are just boxes
for storing top/heads of the linked lists. Here is layout
of containers:
\begin{lstlisting}
#container { record_name,
id,
top,
entries_count }
\end{lstlisting}
\subsection{Extending Schema}
Usually you only need to specify custom Mnesia indexes and tables tuning.
Riak and KAI backends don't need it. Group your table into table packages
represented as modules with handle\_notice API.
\begin{lstlisting}
-module(kvs_feed).
-inclue_lib("kvs/include/kvs.hrl").
metainfo() ->
#schema{name=kvs,tables=[
#table{ name = feed, container = true,
fields = record_info(fields,feed)},
#table{ name = entry, container = feed,
fields = record_info(fields,entry),
keys = [feed_id,entry_id,from] },
#table{ name = comment, container = feed,
fields = record_info(fields,comment),
keys = [entry_id,author_id] } ]}.
\end{lstlisting}
And plug it into schema sys.config:
\begin{lstlisting}
{kvs, {schema,[kvs_user,kvs_acl,kvs_feed,kvs_subscription]}},
\end{lstlisting}
After run you can create schema on local node with:
\begin{lstlisting}
1> kvs:join().
\end{lstlisting}
It will create your custom schema.
\subsection{KVS API}
\subsection{Service}
System functions for start and stop service:
\vspace{1\baselineskip}
\begin{lstlisting}
-spec start() -> ok | {error,any()}.
-spec stop() -> stopped.
\end{lstlisting}
\vspace{1\baselineskip}
\subsection{Schema Change}
This API allows you to create, initialize and destroy the database schema.
Depending on database the format and/or feature set may differ. {\bf join/1} function
is used to initialize database, replicated from remote node along with its schema.
\vspace{1\baselineskip}
\begin{lstlisting}
-spec destroy() -> ok.
-spec join() -> ok | {error,any()}.
-spec join(string()) -> [{atom(),any()}].
-spec init(atom(), atom()) -> list(#table{}).
\end{lstlisting}
\vspace{1\baselineskip}
\subsection{Meta Info}
This API allows you to build forms from table metainfo.
You can also use this API for metainfo introspection.
\vspace{1\baselineskip}
\begin{lstlisting}
-spec modules() -> list(atom()).
-spec containers() -> list(tuple(atom(),list(atom()))).
-spec tables() -> list(#table{}).
-spec table(atom()) -> #table{}.
-spec version() -> {version,string()}.
\end{lstlisting}
\vspace{1\baselineskip}
\subsection{Chain Ops}
This API allows you to modify the data, chained lists.
You can use {\bf create/1} to create the container.
You can add and remove nodes from lists.
\vspace{1\baselineskip}
\begin{lstlisting}
-spec create(atom()) -> integer().
-spec remove(tuple()) -> ok | {error,any()}.
-spec remove(atom(), any()) -> ok | {error,any()}.
-spec add(tuple()) -> {ok,tuple()} |
{error,exist} |
{error,no_container}.
\end{lstlisting}
\vspace{1\baselineskip}
\subsection{Raw Ops}
These functions will patch the Erlang record inside database.
\vspace{1\baselineskip}
\begin{lstlisting}
-spec put(tuple()) -> ok | {error,any()}.
-spec delete(atom(), any()) -> ok | {error,any()}.
\end{lstlisting}
\vspace{1\baselineskip}
\subsection{Read Ops}
Allows you to read the Value by Key and list records with given secondary indexes.
{\bf get/3} API is used to specify default value.
\vspace{1\baselineskip}
\begin{lstlisting}
-spec index(atom(), any(), any()) -> list(tuple()).
-spec get(atom(),any(), any()) -> {ok,any()}.
-spec get(atom(), any()) -> {ok,any()} |
{error,duplicated} |
{error,not_found}.
\end{lstlisting}
\vspace{1\baselineskip}
\subsection{Import/Export}
You can use this API to store all database
in a single file when it is possible. It's ok for development but not very good for production API.
\vspace{1\baselineskip}
\begin{lstlisting}
-spec load_db(string()) -> list(ok | {error,any()}).
-spec save_db(string()) -> ok | {error,any()}.
\end{lstlisting}
\vspace{1\baselineskip}