12/08/2018, 15:19

Working with Mnesia in Elixir

Tuần trước, chúng ta đã tìm hiểu về cơ chế lưu trữ của ETS trong bất ký ứng dụng Elixir nào của Erlang. ETS là một phần của OTP và như một nhà phát triển của Elixir bạn có thể sử dụng nó mà không cần cài đặt bất cứ thứ gì. Điều này là khá điên khi bạn nghĩ về nó, những thứ mà ngôn ngữ khác có thể ...

Tuần trước, chúng ta đã tìm hiểu về cơ chế lưu trữ của ETS trong bất ký ứng dụng Elixir nào của Erlang.

ETS là một phần của OTP và như một nhà phát triển của Elixir bạn có thể sử dụng nó mà không cần cài đặt bất cứ thứ gì. Điều này là khá điên khi bạn nghĩ về nó, những thứ mà ngôn ngữ khác có thể làm giống vượt qua cả giới hạn của Redis? Vâng, câu truyện có lẽ hơi điên một tí ...

Erlang thực sự chạy với hệ thống quản lý cơ sở dữ liệu được phân phối theo thời gian thực được gọi là Mnesia! Trong bài hướng dẫn hôm nay, chúng ta sẽ xem xét cách làm của Mnesia trong Elixir.

What is Mnesia?

Mnesia là một "hệ thống phân phối quản lý cơ sở dữ liệu thời gian thực mềm", giống như ETS, nó hoạt động như một phần của OTP. Điều này có nghĩa là giống như một nhà phát triển của Elixir, bạn có thể sử dụng Mnesia như là mặc định mà không phải phụ thuộc vào cài đặt của bên thứ ba nào cả.

Mnesia thực sự được xây dựng ở vị trí đầu tiên của ETS và DETS để cung cấp các chức năng bổ sung mà bạn có thể mong đợi từ một cơ chế lưu trữ. Ví dụ, Mnesia có sự bền bỉ của DETS, với hiệu suất của ETS. Điều này cho phép bạn lưu trữ thông tin của bạn trên RAM hoặc disk, nhưng nó cũng có nghĩa là bạn có những hạn chế kích thước của DETS.

Transactions là một đặc điểm quan trọng khác của Mnesia mà bạn có thể mong đợi từ một cơ chế lưu trữ. Mnesia cho phép bạn thực hiện nhiều thao tác trên một hoặc nhiều bảng như một transactions đơn lẻ.

Mnesia cũng cho phép bạn lưu trữ dữ liệu của bạn trên nhiều nodes, nó mang lại cho bạn lợi ích của lưu trữ phân phối, nhưng cũng có những hạn chế của lưu trữ phân phối. Mnesia thuộc vào phần CP của định lý CAP.

Nhưng có lẽ lợi ích lớn nhất của việc sử dụng Mnesia là một cơ chế lưu trữ là bạn có thể lưu trữ Erlang terms trực tiếp mà không cần phải dịch sang một định dạng khác để lưu trữ.

Starting Mnesia

Như tôi đã đề cập trước đó, Mnesia hoạt động như là một phần của Erlang và OTP và vì thế bạn đã có mọi thứ cần thiết khi cài đặt Elixir.

Mnesia đã có sẵn thông qua sự tương thích của Elixir và Erlang, vì vậy để truy cập vào Mnesia bạn cần sử dụng cú pháp của nó.

Tuy nhiên, trước khi chúng ta có thể bắt đầu sử dụng Mnesia để lưu trữ dữ liệu, trước hết chúng ta cần phải truyền vào các nodes mà nó sẽ sử dụng để chạy:

:mnesia.create_schema([node])

Trong ví dụ trên, tôi truyền node/0 vào như là 1 danh sách vào hàm create_schema/1

Chúng ta chưa nói về các nút phân bố trong lần tìm hiểu Elixir này, nhưng về mặt lý thuyết nếu chúng ta sử dụng nhiều nodes chúng ta có thể truyền một danh sách nút vào đó. Đây là cách Mnesia được phân phối, bởi vì nó chạy trên nhiều nút mà bạn có thể định nghĩa giống như thế.

Next we can call start/0 to actually start Mnesia Tiếp theo, chúng ta gọi start/0 để bắt đầu chạy Mnesia

:mnesia.start

Nếu chúng ta đang chạy Mnesia trên nhiều nodes, chúng ta sẽ phải gọi hàm này tương ứng với mỗi node.

Creating tables

Để tạo một bảng mới, bạn sử dụng hàm create_table / 2 và truyền 2 tham số, tham số đầu là tên bảng và tham số thứ 2 là danh sách tên các cột của bẳng đó:

:mnesia.create_table(User, [attributes: [:id, :first_name, :last_name, :username, :email]])

Inserting data

Để insert dữ liệu, bạn truyền một anonymous function vào hàm transaction/1. Bên trong transaction sử dụng hàm write / 1 với tham số làb dữ liệu mà bạn muốn insert:

:mnesia.transaction(fn ->
  :mnesia.write({User, 1, "Philip", "Brown", "philipbrown", "phil@ipbrown.com"})
end)

Bạn có thể chèn nhiều rows trong cùng một transaction. Nếu giao dịch thành công, bạn sẽ được trả về một bộ kết quả trong đó phần tử đầu tiên là atom: atom và phần tử thứ hai là atom: ok.

Reading data

To read data, you pass an to the transaction/1 function again, but this time you use the read/1 function to select the row you want: Để đọc dữ liệu, bạn cần truyền một anonymous function vô danh vào hàm transaction/1 giống như là insert, nhưng lần này bạn sử dụng hàm read / 1 để chọn record mà bạn muốn:

{:atomic, [record]} = :mnesia.transaction(fn ->
  :mnesia.read({User, 1})
end)

Nếu có thể tìm thấy dứ liệu thì bạn sẽ nhận được một bộ dứ liệu với phần từ đầu tiên là atom :atomic và phần từ thứ 2 là một danh sách các kết quả tìm được.

Conclusion

Mnesia là một khía cạnh khá thú vị của việc sử dụng Erlang và Elixir, vì thực tế đơn giản đó là một phân phối, một hệ thống quản lý cơ sở dữ liệu thời gian thực mềm, được hoạt động như một phần của ngôn ngữ.

Đó là một điều khá độc đáo khi nói đến những gì bạn nhận được vượt ra khỏi phạm vi của ngôn ngữ lập trình mà bạn lựa chọn.

Mnesia is really quite powerful and so we have only really just scratched the surface of what is possible in today’s tutorial. I’m still trying to get my head around Mnesia myself. Mnesia thực sự rất mạnh mẽ và những thứ mà hôm nay chúng ta đề cập chỉ là mới chạm tới bề mặt của nó mà thôi. Tôi vẫn đang cố gắng trau dồi kiến thức của bản thân mình về Mnesia .

Từ những gì tôi đã đọc, có vẻ như Mnesia có một cách ngọt ngào để lưu trữ một lượng dữ liệu hạn chế trên một số node cố định. Điều này có lẽ là lý tưởng nếu bạn muốn giữ dữ liệu, nhưng bạn không muốn đau đầu của việc xử lý cơ sở dữ liệu.

Và tất nhiên, Mnesia cho phép bạn lưu giữu dữ liệu ngay trong Erlang (hoặc Elixir)!

Khi chúng ta tiếp tục khám phá Elixir, tôi chắc chắn rằng chúng ta sẽ gặp phải một số vấn đề mà chúng ta có thể sử dụng Mnesia để lưu trữ, nếu sử dụng theo cách truyền thống thì chúng ta phải cần có sự phụ thuộc vào cơ sở dữ liệu bên ngoài.

Bổ sung

Bài viêt trên được dịch từ working-mnesia-elixir

Trong bài dịch trên thì hầu hết tác giả viết ra các câu lệnh để gọi trong script của Elixir nhưng kết quả thì không được thể hiện nên các bạn có thể sẽ khó hình dung và theo dõi. Chính vì vậy mà mình sẽ update thêm kết quả ở dưới đây tương ứng với các câu lệnh trên một các trực quan hơn.

iex(1)> :mnesia.create_schema([node])
:ok
iex(2)> :mnesia.start
:ok
iex(3)> :mnesia.create_table(User, [attributes: [:id, :first_name, :last_name, :username, :email]])
{:atomic, :ok}
iex(4)> :mnesia.transaction(fn ->
...(4)>   :mnesia.write({User, 1, "Philip", "Brown", "philipbrown", "phil@ipbrown.com"})
...(4)> end)
{:atomic, :ok}
iex(5)> {:atomic, [record]} = :mnesia.transaction(fn ->
...(5)>   :mnesia.read({User, 1})
...(5)> end)
{:atomic, [{User, 1, "Philip", "Brown", "philipbrown", "phil@ipbrown.com"}]}

Khi bạn muốn stop node vừa khởi động thì sử dụng hàm stop

iex(6)> :mnesia.stop
:stopped
iex(7)> 
15:29:27.745 [info]  Application mnesia exited: :stopped

nil

Khi đã stop rồi thì những record được tạo ra trước đó sẽ bị xóa đi. Nhưng db schema và table thì vẫn còn được lưu trữ lại. Khi stop node đó thì bạn không thể truy cập vào database nữa.

iex(8)> {:atomic, [record]} = :mnesia.transaction(fn ->                                 
...(8)>   :mnesia.read({User, 1})
...(8)> end)
** (MatchError) no match of right hand side value: {:aborted, {:node_not_running, :nonode@nohost}}
    
iex(8)> :mnesia.create_table(User, [attributes: [:id, :first_name, :last_name, :username, :email]])
{:aborted, {:node_not_running, :nonode@nohost}}
iex(9)> :mnesia.create_schema([test_node])                                                         
** (CompileError) iex:9: undefined function test_node/0

Khi bạn start thì lại truy cập được.

iex(16)> :mnesia.start
:ok
iex(17)> {:atomic, [record]} = :mnesia.transaction(fn ->
...(17)>   :mnesia.read({User, 1})
...(17)> end)
** (MatchError) no match of right hand side value: {:atomic, []}

Câu lệnh trên cho thấy bạn có thể truy cập vào đúng node vừa nãy stop nhưng dữ liệu trong bảng đã bị xóa đi nên không thể tìm kiếm được.

iex(17)> :mnesia.create_table(User, [attributes: [:id, :first_name, :last_name, :username, :email]])
{:aborted, {:already_exists, User}}

Với kết quả như trên ta thấy không thể tạo bảng có tên User nữa do đã tạo trước đó. => Cấu trúc bảng được lưu trữ lại nhưng dữ liệu trong đó bị xóa đi. Nó giống như ta truncate database trong MySQL

0